#!/usr/bin/perl
#
# Generate I/O-device boilerplate for firmware and Verilog
#

use integer;
use strict;
use File::Spec;

# Variables from configuration file
our %consts;
our $iodev_addr_bits;
our $iodev_addr_shift;
our $xdev_addr_bits;
our $xdev_addr_shift;
our @sysirqs;
our @iodevs;

sub base($$$) {
    my($num,$bits,$shift) = @_;

    my $v = ($num | (~0 << $bits)) << $shift;
    return $v & 0xffffffff;
}

sub generate_h($) {
    my($out) = @_;

    print $out "#ifndef IODEVS_H\n";
    print $out "#define IODEVS_H\n\n";

    foreach my $c (sort(keys(%consts))) {
	printf $out "#define %-23s 0x%08x /* %d */\n",
	    $c, $consts{$c}, $consts{$c};
    }
    print $out "\n";

    my $ndev  = 0;
    my $nxdev = 0;
    my $nirq  = 0;

    foreach my $sysirq (@sysirqs) {
	printf $out "#define %-23s %d\n", "\U$sysirq\E_IRQ", $nirq++;
    }

    foreach my $dev (@iodevs) {
	my $dcount = $dev->{-count};
	my $icount = length($dev->{-irq});

	$dcount = 1 unless (defined($dcount));

	next unless ($dcount);

	my $name = uc($dev->{-name});
	my $xdev = $dev->{-xdev};

	if ($xdev) {
	    printf $out "\n#define %-23s %d\n", $name.'_XDEV', $nxdev;
	    printf $out "#define %-23s 0x%08x\n", $name.'_BASE',
		base($nxdev, $xdev_addr_bits, $xdev_addr_shift);
	} else {
	    printf $out "\n#define %-23s %d\n", $name.'_DEV', $ndev;
	    printf $out "#define %-23s 0x%08x\n", $name.'_BASE',
		base($ndev, $iodev_addr_bits, $iodev_addr_shift);
	}
	printf $out "#define %-23s %d\n", $name.'_DEV_COUNT', $dcount;
	if ($icount) {
	    printf $out "#define %-23s %d\n", $name.'_IRQ', $nirq;
	}

	$ndev  += $xdev ? 0 : $dcount;
	$nxdev += $xdev ? $dcount : 0;
	$nirq  += $dcount * $icount;
    }

    printf $out "\n#define %-23s %d\n", 'IRQ_VECTORS', $nirq;

    print $out "\n#endif /* IODEVS_H */\n";
}

sub generate_irqtbl($)
{
    my($out) = @_;

    my $nirq = 0;
    my @irqtbl = ();

    print $out "#include \"picorv32.h\"\n";
    print $out "#include \"irq.h\"\n\n";

    foreach my $sysirq (@sysirqs) {
	push(@irqtbl, [$sysirq, 1]);
	$nirq++;
    }

    foreach my $dev (@iodevs) {
	my $dcount = $dev->{-count};
	my $icount = length($dev->{-irq});

	$dcount = 1 unless (defined($dcount));

	next unless ($dcount && $icount);

	my $name = $dev->{-name};

	push(@irqtbl, [$name, $dcount*$icount]);
	$nirq += $dcount*$icount;
    }

    print $out "static IRQHANDLER(spurious)\n";
    print $out "{\n";
    print $out "\tmask_irq(vector);\n";
    print $out "}\n\n";

    foreach my $irq (@irqtbl) {
	printf $out "IRQHANDLER(%s) __attribute__((weak,alias(\"irqhandler_spurious\")));\n", $irq->[0];

    }
    print $out "\nirqhandler_t __attribute__((section(\".sdata\")))\n";
    printf $out "__irq_handler_table[%d] = {\n", $nirq;
    foreach my $irq (@irqtbl) {
	for (my $i = 0; $i < $irq->[1]; $i++) {
	    printf $out "\tirqhandler_%s,\n", $irq->[0];
	}
    }
    print $out "};\n\n";

}

sub generate_verilog($)
{
    my($out) = @_;

    foreach my $c (sort(keys(%consts))) {
	printf $out "\tlocalparam %-23s = \'h%08x; // %d\n",
	    $c, $consts{$c}, $consts{$c};
    }
    print $out "\n";

    my $ndev = 0;
    my $nxdev = 0;
    my $nirq = scalar(@sysirqs);
    my $irq_edge = 0;

    my @imux = ();
    my @xmux = ();
    my @wait = ();
    my @valid = ();
    my @irqs = ();

    print $out "\treg  [31:0] nxdev_rdata;\n";
    print $out "\treg  [31:0] iodev_rdata;\n";
    printf $out "\twire [%2d:0] xdev_valid  = iodev_mem_valid << cpu_mem_addr[%d:%d];\n",
	(1 << $xdev_addr_bits)-1,
	$xdev_addr_shift+$xdev_addr_bits-1, $xdev_addr_shift;
    printf $out "\twire [%2d:0] iodev_valid = xdev_valid[%d] << cpu_mem_addr[%d:%d];\n",
	(1 << $iodev_addr_bits)-1, (1 << $xdev_addr_bits)-1,
	$iodev_addr_shift+$iodev_addr_bits-1, $iodev_addr_shift;

    print $out "\n";

    foreach my $dev (@iodevs) {
	my $dcount = $dev->{-count};
	my $irq    = $dev->{-irq};
	my $xdev   = $dev->{-xdev};

	$dcount = 1 unless (defined($dcount));

	next unless ($dcount);

	my $name = $dev->{-name};

	my $didx = ($dcount > 1) ? sprintf('[0:%d]', $dcount-1) : '';

	printf $out "\twire [31:0] iodev_rdata_%s%s;\n", $name, $didx;
	if ($irq ne '') {
	    printf $out "\twire [%2d:0] iodev_irq_%s%s;\n",
		length($irq)-1, $name, $didx;
	}

	if ($xdev) {
	    printf $out "\twire [%2d:0] iodev_valid_%s = xdev_valid[%d:%d];\n",
		$dcount-1, $name, $nxdev+$dcount-1, $nxdev;
	} else {
	    printf $out "\twire [%2d:0] iodev_valid_%s = iodev_valid[%d:%d];\n",
		$dcount-1, $name, $ndev+$dcount-1, $ndev;
	}

	printf $out "\ttri1 [%2d:0] iodev_wait_n_%s;\n", $dcount-1, $name;
	push(@wait, "(&iodev_wait_n_$name)");

	print $out "\n";

	for (my $d = 0; $d < $dcount; $d++) {
	    my $dsuf = ($dcount > 1) ? "[$d]" : '';

	    if ($xdev) {
		push(@xmux, "iodev_rdata_$name$dsuf");
	    } else {
		push(@imux, "iodev_rdata_$name$dsuf");
	    }
	    for (my $i = 0; $i < length($irq); $i++) {
		my $isuf = "[$i]";
		my $type = substr($irq,$i,1);

		push(@irqs, "iodev_irq_$name$dsuf$isuf");

		if ($type eq 'e') {
		    $irq_edge |= 1 << $nirq;
		}

		$nirq++;
	    }
	    if ($xdev) {
		$nxdev++;
	    } else {
		$ndev++;
	    }
	}
    }

    print  $out "\t// I/O input MUX\n";
    print  $out "\talways_comb\n";
    printf $out "\t\tcase (cpu_mem_addr[%d:%d])\n",
	$xdev_addr_shift+$xdev_addr_bits-1, $xdev_addr_shift;
    my $nxdev = 0;
    foreach my $dev (@xmux) {
	printf $out "\t\t\t%d'd%d:\t iodev_rdata = %s;\n",
	    $xdev_addr_bits, $nxdev++, $dev;
    }
    printf $out "\t\t\t%d'd%d:\n",
	    $xdev_addr_bits, (1 << $xdev_addr_bits)-1;
    printf $out "\t\t\tcase (cpu_mem_addr[%d:%d])\n",
	$iodev_addr_shift+$iodev_addr_bits-1, $iodev_addr_shift;
    my $ndev = 0;
    foreach my $dev (@imux) {
	printf $out "\t\t\t\t%d'd%d:\t iodev_rdata = %s;\n",
	    $iodev_addr_bits, $ndev++, $dev;
    }
    print $out "\t\t\t\tdefault: iodev_rdata = 32'hxxxxxxxx;\n";
    print $out "\t\t\tendcase\n";
    print $out "\t\t\tdefault: iodev_rdata = 32'hxxxxxxxx;\n";
    print $out "\t\tendcase\n";

    print $out "\n";

    print $out "\ttri0 [31:0] sys_irq;\n";
    my $nirq = scalar(@sysirqs);
    foreach my $irq (@irqs) {
	printf $out "\tassign sys_irq[%2d] = %s;\n", $nirq++, $irq;
    }
    print $out "\n";

    printf $out "\tlocalparam [31:0] irq_edge_mask =  32'h%08x;\n", $irq_edge;
    printf $out "\tlocalparam [31:0] irq_masked    = ~32'h%08x;\n\n",
	((1 << $nirq)-1);

    printf $out "\twire iodev_wait_n = ";
    if (scalar(@wait)) {
	print $out join(" & \n\t\t", @wait);
    } else {
	print $out "1'b1";
    }
    print $out ";\n";
}

my($mode, $infile, $outfile) = @ARGV;

unless (defined(do File::Spec->rel2abs($infile))) {
    die "$0: $infile: $@\n"
}

# Export these as constants, too
$consts{'IODEV_ADDR_BITS'}  = $iodev_addr_bits;
$consts{'IODEV_ADDR_SHIFT'} = $iodev_addr_shift;
$consts{'XDEV_ADDR_BITS'}   = $xdev_addr_bits;
$consts{'XDEV_ADDR_SHIFT'}  = $xdev_addr_shift;

open(my $out, '>', $outfile) or die;

if ($mode eq 'h') {
    generate_h($out);
} elsif ($mode eq 'c') {
    generate_irqtbl($out);
} elsif ($mode eq 'v') {
    generate_verilog($out);
}

close($out);
