#! /usr/bin/perl


### v1.0 Written Fall 2003 by Kurt Meinz
### v2.4 Update Summer 2004 for 61C
###      - mfc0
###      - EPC, Cause, Status
###      - syscall
###      - halt
###      - rfe

use Getopt::Long;

$USAGE = '  Command Line Options:
    -v[erbose]             -- Optional; Print helpful info to stdout
    -d[ebug]               -- Optional, Print lots of stuff to stdout
    -h[elp]                -- Optional; Print help on assembly
    -c[lass]               -- Optional; Specifies class extensions. Default: none.
    -i[nputfile]           -- File that contains MIPS assembly
    -o[outputfile]         -- File in which to write machine code
  Usage:
    mipsasm [-v] [-d] -i <filename> -o <filename>
';

do_get_args();
do_open_files();
do_setup_hashes();
do_load_labels();
do_make_machine_code();
exit(0);

sub do_get_args() {
    $VERBOSE = 0;   $DEBUG = 0;
    $IN_FILE = 0;   $OUT_FILE = 0;
    $CLASS = "none";

    $num_args = $#ARGV;
    $ops = GetOptions("verbose" => \$VERBOSE,
		      "debug" => \$DEBUG,
		      "help" => \$need_help,
		      "class=s" => \$CLASS,
		      "inputfile=s" => \$IN_FILE,
		      "outputfile=s" => \$OUT_FILE);

    if ($need_help == 1) {
	print_help();
	exit(0);
    } elsif ($ops == 0 || $num_args < 3 || $num_args > 7) {
	print $USAGE;
	exit(0);
    } elsif ($DEBUG) {
	$VERBOSE = 1;
    }
    
    print "[*] Starting mipsasm:\n" if $VERBOSE;
    print "     Options: verbose=$VERBOSE debug=$DEBUG class=$CLASS " .
	  "inputfile=$IN_FILE outputfile=$OUT_FILE\n" if $VERBOSE;

}

sub do_open_files() {
    if (!(-e $IN_FILE)) {
	die "Cannot find input file \"$IN_FILE\"\n";
    } elsif (!open(IN, "<$IN_FILE")) {
	die "Cannot open input file \"$IN_FILE\": $!\n";	
    }
    if (!open(OUT, ">$OUT_FILE")) {
	die "Cannot open output file \"$OUT_FILE\": $!\n";	
    }
}

sub do_load_labels() {
    $line_counter = 0; #current line of input file
    $inst_counter = 0; #address of label/instruction if found on this line
    print "   [l] Starting Pass 1: Scanning for labels in assembly code...\n" if $VERBOSE;

    seek(IN, 0, 0); # Go to beginning
    while ($line = <IN>) {
	$line_counter++;
	$line_instrs{$line_counter} = $inst_counter;
	chomp $line;
	
	#clean up the line
	$line =~ s/\#.*$//; #Get rid of comments
	$line =~ s/,/ /g;   #I hate commas!
	$line =~ s/\s+/ /g; #Make all whitespace one character
        $line =~ s/^ //;    #Bugfix: remove leading whitespace.

	printf("    [l] Label-scanning line $line_counter (address:0x%08x): $line\n", $inst_counter) if $DEBUG;
	
	if ( $line =~ /\.(address)\s+(\S+)\s*$/ ) {
	    $args = $2;
	    print "     Found assembler directive \"address\" with arg $args\n" if $DEBUG;
	    if ($args =~ /0x(........)/) {
		$val = $1;
		print "     Setting current address to 0x$val\n" if $DEBUG;
		$dec = hex($val);
		print "     (In word-addressed decimal: $dec)\n" if $DEBUG;
		$inst_counter = $dec;
	    } else {
		print "     FATAL ERROR: Line $line_counter: $line\n";
		print "     Invalid .address argument. Must use 32-bit hex prefaced with '0x'!\n";
		exit(0);
	    }
	}	

	if ( $line =~ /^(.+):/ ) {
	    $label = $1;
	    print "     Found label $1\n" if $DEBUG;
	    if ($label_names{$label}) {
		print "     WARNING: DUPLICATE LABEL FOUND!\n";
		print "     On line $line_counter (\"$line\")\n";
		printf("     Using new address 0x%08x; old addr was 0x%08x\n", 
		       $inst_counter, $label_vals{$label});
	    }
	    printf("     Label $label will resolve to address 0x%08x\n", $inst_counter) if $VERBOSE;
	    $label_names{$label} = $label;
	    $label_vals{$label} = $inst_counter;
	    if ( $line =~ /^.+:\s*\S+/ && $line !~ /\.address/) {
		print "     Found instruction. Line will count against instruction count.\n" if $DEBUG;
		$inst_counter += 4;
	    } else {
		print "     No instruction/data found. Line will not count against instruction count.\n" if $DEBUG;
	    }
	} elsif ( $line =~ /^s*\.(\S+)/ ) {
	    $direct = $1;
	    print "     Found assembler directive $direct.\n" if $DEBUG;
	    if ($direct eq "word") {
		print "     Found .word directive. Incrementing instruction count.\n" if $DEBUG;
		$inst_counter += 4;
	    } elsif ($direct eq "count") {
		print "     Found .count directive. Incrementing instruction count.\n" if $DEBUG;
		$inst_counter += 4;
	    } elsif ($direct eq "la") {
		print "     Found .la directive. Incrementing instruction count.\n" if $DEBUG;
		$inst_counter += 4;
	    } elsif ($direct eq "address") {
		#already handled.
	    } else {
		print "Unrecognized assembler directive $line. FATAL ERROR!!";
		exit(0);
	    }
	} elsif ( $line =~ /\S+/ && $line !~ /\.address/) {
	    print "     Found non-labelled instruction line. Incrementing address.\n" if $DEBUG;
	    $inst_counter += 4;
	} else {
	    print "     Found non-instruction line. Ignoring.\n" if $DEBUG;
	}
    }

    $line_instrs{$line_counter+1} = $inst_counter;

    if ($DEBUG) {
	print "   [l] Done scanning labels. Here's my label-to-address hash:\n";
	$found_labels = 0;
	for my $key (keys %label_vals) {
	    printf("       $key => 0x%08x\n", $label_vals{$key});
	    $found_labels++;
	}
	if ($found_labels eq "0") {
	    print "       [empty: no labels in assembly]\n";
	} else {
	    print "       [$found_labels labels have been hashed]\n";
	}
	print "   [l] Here's my line-to-address map:\n";
	for ($i = 1; $i <= $line_counter + 1; $i++) {
	    printf("     Line $i => 0x%08x\n", $line_instrs{$i});
	}
    }
}

sub do_make_machine_code() {
    $line_counter = 0; #current line of input file
    $inst_counter = 0; #address of label/instruction if found on this line
    seek(IN, 0, 0);    # Go to beginning
    print "   [m] Starting Pass 2: Machine-coding assembly...\n" if $VERBOSE;
    $last_line_val = "Should not see me";
    while ($line = <IN>) {
	$line_counter++;
	chomp $line;

	$orig_line = $line;

	#clean up the line
	$line =~ s/\#.*$//; #Get rid of comments
	$line =~ s/,/ /g;   #I hate commas!
	$line =~ s/\s+/ /g; #Make all whitespace one character

	printf("    [m] Machine-coding line $line_counter (address:0x%08x): $line\n", $inst_counter) if $DEBUG;

	#test for instruction count discrepency
	if ($inst_counter != $line_instrs{$line_counter}) {
	    $last_line_num = $line_counter - 1;
	    print "     FATAL ERROR!!\n";
	    print "       You have a typo or an unimplemented instruction somewhere around these two lines:\n";
	    print "       Line $last_line_num: $last_line_val\n";
	    print "       Line $line_counter: $line\n";	    
	    print OUT "FATAL ERROR!!\n";
	    print OUT "You have a typo or an unimplemented instruction somewhere around these two lines:\n";
	    print OUT "Line $last_line_num: $last_line_val\n";
	    print OUT "Line $line_counter: $line\n";	    


	    #printf("       Label-count  says we should be at address %08x.\n", $line_instrs{$line_counter});
	    #printf("       Machine-code says we should be at address %08x.\n", $inst_counter);
	    exit(0);
	}

	# Code them up!
	if ($line =~ /(add|addu|and|or|slt|sltu|sub|subu|xor)\s+\$(\S+)\s+\$(\S+)\s+\$(\S+)/) {
	    $op = $1; $rd = $2; $rs = $3; $rt = $4;
	    $inst = make_RFormat($op_codes{$op}, $rs, $rt, $rd, 0, $func_codes{$op});
	    print OUT sprintf("%08x\n", $inst); # kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(sll|sra|srl)\s+\$(.+)\s+\$(.+)\s+(\S+)\s*$/) {
	    $op = $1; $rd = $2; $rt = $3; $shamt = $4;
	    $inst = make_RFormat($op_codes{$op}, 0, $rt, $rd, $shamt, $func_codes{$op});
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ( $line =~ /(mfhi|mflo)\s+\$(\S+)\s*/ ) {
	    $op = $1; $rd = $2;
	    $inst = make_RFormat($op_codes{$op}, 0, 0, $rd, 0, $func_codes{$op});
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(addi|addiu|andi|ori|slti|sltiu|xori)\s+\$(\S+)\s+\$(\S+)\s+(\S+)/) {
	    $op = $1; $rt = $2; $rs = $3; $im_unparsed = $4;
	    $im = parse_immed($im_unparsed);
	    $inst = make_IFormat($op_codes{$op}, $rs, $rt, $im);
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(lb|lbu|lw|sb|sw)\s+\$(.+)\s+(.+)\(\$(.+)\)/) {
	    $op = $1; $rt = $2; $im_unparsed = $3; $rs = $4;
	    $im = parse_immed($im_unparsed);
	    $inst = make_IFormat($op_codes{$op}, $rs, $rt, $im);
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(bne|beq)\s+\$(.+)\s+\$(.+)\s+(\S+)\s*$/) {
	    $op = $1; $rs = $2; $rt = $3; $im_unparsed = $4;
	    #To do: Warn about putting real values in immed here ...
	    $im = (parse_immed($im_unparsed) - $line_instrs{$line_counter} - 4) >> 2;
	    $inst = make_IFormat($op_codes{$op}, $rs, $rt, $im);
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(bgez|bltz)\s+\$(.+)\s+(\S+)\s*$/) {
	    $op = $1; $rs = $2; $im_unparsed = $3;
	    #To do: Warn about putting real values in immed here ...
	    $im = (parse_immed($im_unparsed) - $line_instrs{$line_counter} - 4) >> 2;
	    $rt = ($op eq "bgez") ? 1 : 0;
	    $inst = make_IFormat($op_codes{$op}, $rs, $rt, $im);
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(j|jal)\s+(\S+)\s*$/) {
	    $op = $1; $target_unparsed = $2;
	    #To do: Warn about putting real values in immed here ...
	    $target = parse_immed($target_unparsed) >> 2;
	    $inst = make_JFormat($op_codes{$op}, $target);
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(break)\s+(\S+)\s*$/) {
	    $op = $1; $code_unparsed = $2;
	    #To do: Warn about putting real values in immed here ...
	    $code = parse_immed($code_unparsed);
	    $inst = make_BFormat($op_codes{$op}, $code);
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(syscall|rfe)\s*$/) {
	    $op = $1; $rs = ($op eq "rfe") ? 16 : 0;
	    $inst = make_RFormat($op_codes{$op}, $rs, 0, 0, 0, $func_codes{$op});
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(halt)\s*$/) {
	    $op = $1;
	    $inst = make_RFormat($op_codes{$op}, 31, 31, 31, 31, $func_codes{$op});
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(jr)\s+\$(\S+)\s*$/) {
	    $op = $1; $rs = $2;
	    $inst = make_RFormat($op_codes{$op}, $rs, 0, 0, 0, $func_codes{$op});
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(mult|multu|div|divu)\s+\$(\S+)\s\$(\S+)\s*$/) {
	    $op = $1; $rs = $2; $rt = $3;
	    $inst = make_RFormat($op_codes{$op}, $rs, $rt, 0, 0, $func_codes{$op});
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(mfc0|mtc0)\s+\$(\S+)\s\$(\S+)\s*$/) {
	    $op = $1; $rs = ($op eq "mtc0") ? 4 : 0; $rt = $2; $rd = parse_c0_reg($3);
	    $inst = make_RFormat($op_codes{$op}, $rs, $rt, $rd, 0, 0);
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /(lui)\s+\$(.+)\s+(\S+)\s*$/) {
	    $op = $1; $rt = $2; $im_unparsed = $3;
	    #To do: Warn about putting real values in immed here ...
	    $im = parse_immed($im_unparsed);
	    if ($label_names{$im_unparsed} eq $im_unparsed) {
		#For a lui, load the upper bits of an address
		print "     Lui immed ($im_unparsed) is an address. Using top 16 bits.\n" if $DEBUG;
		$im = $im >> 16;
	    }
	    $inst = make_IFormat($op_codes{$op}, 0, $rt, $im);
	    print OUT sprintf("%08x\n", $inst); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /\.address/) {
	    $inst_counter = $line_instrs{$line_counter+1};
	    printf("     Found address directive... adjusting current address to %08x\n", $inst_counter)
		if $DEBUG;
	} elsif ($line =~ /\.word\s+(\S+)\s*$/) {
	    $val_unparsed = $1;
	    $val = parse_immed($val_unparsed);

	    if ($DEBUG) {
		printf("     Found .word directive... setting value at addr 0x%08x to 0x%08x\n",
		       $inst_counter, $val);
	    } elsif ($VERBOSE) {
		printf("     Line $line_counter: 0x%08x:0x%08x [ $line => Setting value at addr 0x%08x to 0x%08x]\n",
		       $inst_counter, $val, $inst_counter, $val);
	    }
	    print OUT sprintf("%08x\n", $val); #kurt
	    $inst_counter += 4;
	} elsif ($line =~ /\.count\s+(\S+)\s+(\S+)\s+(\S+)\s*$/) {
	    $denom = $1; $arg1_u = $2; $arg2_u = $3;
	    $arg1 = parse_immed($arg1_u);
	    $arg2 = parse_immed($arg2_u);
	    $count = ($arg2 - $arg1) / (($denom eq "bytes") ? 1 : 4);
	    if ($DEBUG) {
		printf("     $arg1_u maps to 0x%08x; $arg2_u maps to 0x%08x; Delta is $count $denom.\n",
		       $arg1, $arg2);
		printf("     Found .count directive... from $arg1_u to $arg2_u is 0x%08x $denom.\n",
		       $count);
	    } elsif ($VERBOSE) {

		printf("     Line $line_counter: 0x%08x:0x%08x [ Found .count directive...from $arg1_u to $arg2_u is 0x%08x $denom.]\n",
		       $inst_counter, $count, $count);	
    }
	    print OUT sprintf("%08x\n", $count); #kurt
	    $inst_counter += 4;

	} else {
	    print "     Ignoring line.\n" if $DEBUG;
	}

    $last_line_val = $orig_line;

    }

    if ($inst_counter != $line_instrs{$line_counter+1}) {

	  $last_line_num = $line_counter - 1;
	  print "     FATAL ERROR!\n";
	  print "       You have a typo or an unimplemented instruction somewhere around these two lines:\n";
	  print "       Line $last_line_num: $last_line_val\n";
	  print "       Line $line_counter: $line\n";	    

	  print OUT "FATAL ERROR!\n";
	  print OUT "You have a typo or an unimplemented instruction somewhere around these two lines:\n";
	  print OUT "Line $last_line_num: $last_line_val\n";
	  print OUT "Line $line_counter: $line\n";	    



	#print "     WARNING: INSTRUCTION COUNT MISMATCH!\n";
	#print "       Line $line_counter: $line\n";	    
	#print "       This means you have a typo somewhere at or directly above this line!\n";	    
	#print "       This can also be an unimplemented instruction!\n";
	exit(0);
    }

    print "[*] mipsasm finished successfully.\n" if $VERBOSE;
}

sub parse_reg($) {
    my $reg = shift();
    if (defined $reg_map{$reg}) {
	return $reg_map{$reg};
    } else {
	return $reg;
    }
}

sub parse_c0_reg($) {
    my $reg = shift();
    if (defined $reg_map_c0{$reg}) {
	return $reg_map_c0{$reg};
    } else {
	return $reg;
    }
}



sub parse_immed() {
    my $immedval = shift();
    if ($label_names{$immedval}  ne "") {
	printf("     Found label $immedval => 0x%08x\n", $label_vals{$immedval}) if $DEBUG;
	return $label_vals{$immedval};
    } elsif ($immedval =~ /0x(.+)$/) {
	print "     Found hexadecimal immediate 0x$1\n" if $DEBUG;
	return hex($1);
    } else {
	printf("     Found decimal immediate $immedval (0x%08x)\n", $immedval) if $DEBUG;
	return $immedval;
    }
}

sub make_RFormat() {
    local($mop, $mrs_u, $mrt_u, $mrd_u, $mshamt, $mfunc) = @_;
    local($mrs, $mrt, $mrd, $val_dec);
    $mrs = parse_reg ($mrs_u);
    $mrt = parse_reg ($mrt_u);
    $mrd = parse_reg ($mrd_u);
    $val_dec = (($mop << 26) + ($mrs << 21)   + ($mrt << 16) +
               ($mrd << 11) + ($mshamt << 6) + $mfunc);
    check_instruction($op);
    if ($DEBUG) {
	printf("     RFormat(op:$mop($op) rs:$mrs($mrs_u) rt:$mrt($mrt_u) " .
	       "rd:$mrd($mrd_u) sh:$mshamt func:$mfunc) => 0x%08x\n",
	       $val_dec);
    } elsif ($VERBOSE) {
	printf("     Line $line_counter: 0x%08x:0x%08x [$line => R(op:$mop($op) rs:$mrs($mrs_u) rt:$mrt($mrt_u) " .
	       "rd:$mrd($mrd_u) sh:$mshamt func:$mfunc)] \n",
	       $inst_counter, $val_dec);
    }
	return $val_dec;
}

sub make_IFormat() {
    local($mop, $mrs_u, $mrt_u, $mim) = @_;
    local($mrs, $mrt, $val_dec);
    $mrs = parse_reg($mrs_u);
    $mrt = parse_reg($mrt_u);
    $mim = $mim & 0x0000FFFF;
    $val_dec = ($mop << 26) + ($mrs << 21) + ($mrt << 16) + $mim;
    check_instruction($op);
    if ($DEBUG) {
	printf("     IFormat(op:$mop($op) rs:$mrs($mrs_u) rt:$mrt($mrt_u) " .
	       "immed:0x%08x) => 0x%08x\n",
	       $mim, $val_dec);
    } elsif ($VERBOSE) {
	printf("     Line $line_counter: 0x%08x:0x%08x [$line => I(op:$mop($op) rs:$mrs($mrs_u) rt:$mrt($mrt_u) " .
	       "immed:0x%08x)]\n",
	       $inst_counter, $val_dec, $mim);
    }
    return $val_dec;

}

sub make_JFormat() {
    local($mop, $mim) = @_;
    local($val_dec);
    $mim = $mim & 0x03FFFFFF;
    $val_dec = ($mop << 26) + $mim;
    check_instruction($op);
    if ($DEBUG) {
	printf("     JFormat(op:$mop($op) target:0x%08x >> 2 = 0x%08x)) => 0x%08x\n",
	       $mim << 2, $mim, $val_dec);
    } elsif ($VERBOSE) {
	printf("     Line $line_counter: 0x%08x:0x%08x [$line => JFormat(op:$mop($op) target:0x%08x >> 2 = 0x%08x)]\n",
	       $inst_counter, $val_dec, $mim << 2, $mim) if $VERBOSE;
    }
    return $val_dec;
}

sub make_BFormat() {
    local($mop, $mcode) = @_;
    local($mcode_s, $val_dec);
    $mcode_s = ($mcode << 6) & 0x03FFFFC0;
    $val_dec = ($mop << 26) + $mcode_s + 13;
    check_instruction($op);
    if ($DEBUG) {
	printf("     BFormat(op:$mop($op) code:0x%08x) => 0x%08x\n",
	       $mcode, $val_dec) if $VERBOSE;
    } elsif ($VERBOSE) {
	printf("     Line $line_counter: 0x%08x:0x%08x [$line => BFormat(op:$mop($op) code:$mcode (0x%08x))]\n",
	       $inst_counter, $val_dec, $mcode) if $VERBOSE;
    }	
    return $val_dec;
}

sub check_instruction() {
    local $inst = shift;
    print "     Checking if $inst is in allowed instructions for class $CLASS..." if $DEBUG;
    if ($inst =~ /^$allowed_insts$/) {
	print "ok.\n" if $DEBUG;
    } else {
	  print "     FATAL ERROR!\n";
	  print "       Line $line_counter: $line\n";	    
	  print "       This instruction \'$inst\' is not on the list of implemented instructions for class $CLASS.\n";
	  print "       Allowed instructions are $allowed_insts\n";

	  print OUT "FATAL ERROR!\n";
	  print OUT "Line $line_counter: $line\n";	    
	  print OUT "This instruction \'$inst\' is not on the list of implemented instructions for class $CLASS.\n";
	  print OUT "Allowed instructions are $allowed_insts\n";

	  exit(1);
      }
}	

#
# Some hashes
#

sub do_setup_hashes() {

    %label_names;
    %label_vals;
    %line_instrs;

    %op_codes = ("add"   => "0", # New
		 "addi"  => "8", # New
		 "addu"  => "0", #
		 "addiu" => "9", #
		 "and"   => "0", #
		 "andi"  => "12",#
		 "beq"   => "4", #
		 "bgez"  => "1", #
		 "bltz"  => "1", #
		 "bne"   => "5", #
		 "break" => "0", #
		 "div"   => "0", # New
		 "divu"  => "0", # New
		 "halt"  => "63", ## NEW!
		 "j"     => "2", #
		 "jal"   => "3", #
		 "jr"    => "0", #
		 "lui"   => "15", #
		 "lb"    => "32", #
		 "lbu"    => "36", #
		 "lw"    => "35", #
		 "sw"    => "43", #
		 "sb"    => "40", #
		 "mult"  => "0", # New
		 "multu" => "0", #
		 "mfhi"  => "0", #
		 "mflo"  => "0", #
		 "mfc0"  => "16", # NEW!
		 "mtc0"  => "16", # NEW!
		 "or"    => "0", #
		 "ori"   => "13",#
		 "rfe"   => "10", ## NEW!
		 "sll"   => "0", #
		 "slt"   => "0", #
		 "slti"  => "10",#
		 "sltiu" => "11",#
		 "sltu"  => "0", #
		 "sub"   => "0", # New
		 "subu"  => "0", #
		 "sra"   => "0", #
		 "srl"   => "0", #
		 "sw"    => "43",#
		 "syscall" => "0", ## NEW!
		 "xor"   => "0", #
		 "xori"  => "14"); #

    %func_codes = ("add"   => "32", #new
		   "addi"  => "x", #new
		   "addu"  => "33",
		   "addiu" => "x",
		   "and"   => "36",
		   "andi"  => "x",
		   "beq"   => "x",
		   "bgez"  => "x",
		   "bltz"  => "x",
		   "bne"   => "x",
		   "break" => "13",
		   "div"   => "26", # new
		   "divu"  => "27", # new
		   "halt"  => "63", ## NEW!
		   "j"     => "x",
		   "jal"   => "x",
		   "jr"    => "8",
		   "lui"   => "x",
		   "lw"    => "x",
		   "mult"  => "24",
		   "multu" => "25",
		   "mfhi"  => "16",
		   "mflo"  => "18",
		   "or"    => "37",
		   "ori"   => "x",
		   "rfe"   => "32",  ## NEW!
		   "sll"   => "0",
		   "slt"   => "42",
		   "slti"  => "x",
		   "sltiu" => "x",
		   "sltu"  => "43",
		   "sub"   => "34", #new
		   "subu"  => "35",
		   "sra"   => "3",
		   "srl"   => "2",
		   "sw"    => "x",
		   "syscall" => "12", ## NEW!
		   "xor"   => "38",
		   "xori"  => "x");
    
    %reg_map = ("zero" => 0,
		"at" => 1,
		"v0" => 2,
		"v1" => 3,
		"a0" => 4,
		"a1" => 5,
		"a2" => 6,
		"a3" => 7,
		"t0" => 8,
		"t1" => 9,
		"t2" => 10,
		"t3" => 11,
		"t4" => 12,
		"t5" => 13,
		"t6" => 14,
		"t7" => 15,
		"s0" => 16,
		"s1" => 17,
		"s2" => 18,
		"s3" => 19,
		"s4" => 20,
		"s5" => 21,
		"s6" => 22,
		"s7" => 23,
		"t8" => 24,
		"t9" => 25,
		"k0" => 26,
		"k1" => 27,
		"gp" => 28,
		"sp" => 29,
		"fp" => 30,
		"ra" => 31);

    %reg_map_c0 = ("BadVAddr" => 8,
		   "Status" => 12,
		   "Cause" => 13,
		   "EPC" => 14);

    #Be sure to implement an instruction before adding it to the class lists!

    $CLASS_NONE = "add|addi|addiu|addu|and|andi|beq|bgez|bltz|bne|break|div|divu|halt|j|jal|jr|lui|lb|lbu|lw|mfc0|mtc0|mflo|mfhi|mult|multu|or|ori|rfe|sb|sll|slt|slti|sltiu|sltu|sra|srl|sub|subu|sw|syscall|xor|xori";

    $CLASS_CS152 = "addiu|addu|and|andi|beq|bgez|bltz|bne|break|divu|j|jal|jr|lui|lw|mflo|mfhi|multu|or|ori|sll|slt|slti|sltiu|sltu|sra|srl|subu|sw|xor|xori";

    $CLASS_CS61C = "add|addu|addiu|and|beq|bne|halt|j|jal|jr|lui|lb|lbu|lw|mfc0|or|ori|rfe|sll|srl|sra|slt|slti|sub|sb|sw|syscall";

    $allowed_insts = $CLASS_NONE;
    if ($CLASS =~ /61[cC]/) {
	$allowed_insts = $CLASS_CS61C;
    } elsif ($CLASS =~ /152/) {
	$allowed_insts = $CLASS_CS152;
    } elsif ($CLASS =~ /[nN][oO][nN][eE]/) {
	$allowed_insts = $CLASS_NONE;
    } else {
	print "FATAL ERROR: Unknown class specifier \"$CLASS\".\n";
	print " You must specify either \"cs61c\", \"cs152\", or \"none\"\n";
	exit(0);
    }
}

sub print_help() {
print 'MIPSASM - v2.4

This is a very silly assembler. So far, it works with labels, 
branches, hex, decimal, and 3 directives:

.address [val] : sets the address of the next
               instruction to be [val]. Useful
               for making data mems and non-contiguous
               blocks. This applies only to jumps (since
	       branches are relative)

.word [val]  : writes [val] directly, rather than parsing
               it. Useful for creating data.

.count [denom] [start] [end] : writes the number of [denom]
               between labels [start] and [end], inclusive.
               [denom] must be "words" or "bytes"

[val] itself can be signed decimal, or hex (prefaced with 0x),
or a label (which will resolve to the labels word address).

WARNING: THIS IS NOT A SIMULATOR, nor is it a syntax checker. 
You must make sure that your assembly is typo-free. Ive tried to 
do some error-checking, but be sure to run your program in spim 
before you run it through mipsasm

Please send bugs to Kurt.

------------------------

Example (to build a boot0 segment)

block0:
        .count words f1 b0end # length of block 0
        .word 0x00800000      # block starting address
        .address 0x00800000   # sets the address of the next word
                              # to be this value.
f1:     .word 1
f2:     .word 2
f3:     .word 3
        ...
b0end:  .word 100

block1:
        .count words l1 b1end # length of block
        .word 0x00400004      # address in which to put the start of the block
        .address 0x00400004   # sets the address of the next word to be this
                              # value. labels will resolve to offset off this
                              # address.
l1:	jal l5
	addiu $ra $ra 8
l2:	jal l2
	addiu $ra $ra -8
l3:	jal l4
	addiu $ra $ra -16
l4:	addiu $v0 $v0 0xFFFFF
	addiu $v0 $v0 l4
l5:	j l6
	addiu $v0 $zero 1
l6:	jr $ra
b1end:	addiu $v0 $zero 1	


{Note: This assembler is almost certainly broken in parts.
       If you come across any bugs, please let me know.}
';
}


