/* Rosemary Francis 2004
 * A Java version of the N_105
 */


/* 
   This program has been written to demonstrate the role of each module of the N-105 processor in a 
   relatively concise and readable way. To those of you more familiar with java you may find it useful
   to read this before tackling the verilog. To make this programme follow the harware design as closely 
   as possible I have implemented all the input wires as method arguements and the outputs to be any 
   field modified by the method. The methods have no return value as such. Each field represents a 
   wire or bus (excluding constants declared for readability).
   
   Each method represents a hardware module of the same name with the exceptions of main and init. Main 
   represents the top level module (cpu), which deals with module instantiation and progression
   of instructions through the two stage pipeline. 

   Many of the control flow wires are superfluous to this program but have been left in to 
   indicate their role in the hardware implementation. 
*/

import java.io.*;

public class J_105
{
    static int program_length = 0;

    static char [] data_mem = new char [2048];        		//data memory
    static char [] instruction_mem = new char [(1<<16) - 1]; 	//instruction memory (big!!)

    //opcodes    
    final static byte OR = 0x00;
    final static byte AND = 0x01;
    final static byte XOR = 0x02;
    final static byte NOT = 0x03;

    final static byte MOV = 0x04;
    final static byte ADD = 0x05;
    final static byte SUB = 0x06;
    final static byte CMP = 0x07;

    final static byte LSLI = 0x08;
    final static byte LSRI = 0x09;
    final static byte ASRI = 0x0a;
    final static byte ROTI = 0x0b;

    final static byte MOVI = 0x0c;
    final static byte ADDI = 0x0d;
    final static byte SUBI = 0x0e;
    final static byte CMPI = 0x0f;

    final static byte LD = 0x10;
    final static byte ST = 0x14;

    final static byte BR = 0x18;
    final static byte BSR = 0x19;
    final static byte RET = 0x1a;

    final static byte IFS = 0x1f;

    static boolean reset = true;
    
    static short [] register_file = new short [16];
    static boolean [] reg_val_defined = new boolean [16]; //has the register been initialised?

    //flags
    static boolean [] flags = new boolean [4];
    static boolean [] new_flags = new boolean [4];
    final static byte N = 3;  //sign of result, most sig bit
    final static byte V = 2;  //arithmetic overflow, ie result has the wrong sign
    final static byte Z = 1;  //result is zero
    final static byte C = 0;  //carry out of addition, borrow out of subtraction (17th bit)

    //data flow
    static short alu_result, return_addr, load_result, dest_contents;

    //control flow
    static boolean alu_en, br_unit_en, return_en, ld_st_comming, load_n_store, bsr_en, set_cond_en;
    static boolean write_en = false;
    static boolean skip; //indicates whether the last instruction was an ifs <true>

    //pipeline control
    static Bus instruction = new Bus(16);
    static short next_instruction;
    static boolean instruction_ready;
    static boolean pipeline_advance;
    static boolean mem_is_busy = false;

    //instuction
    static byte opcode;
    static byte imm4; 
    static byte imm7;
    static short imm7_signed; 
    static char imm7_unsigned; 
    static short imm11;

    static byte index_a, index_b;

    //pc
    static short pc;

    //register contents
    static short contents_a, contents_b;

    //branch stuff
    static short new_return_addr;

    public static void main(String [] args)
    {	
	mem_is_busy = !reset;
	instruction_ready = reset;
	pc = (reset) ? 0 : pc; 
	write_en = false;
	
	init();
	
	while(pc < program_length)
	    {
		//stage 1 of the pipeline
		pipeline_advance = !mem_is_busy & instruction_ready;
		
		//instruction_fetch
		System.out.print("\npc :" + (pc*2) + " ");
		instruction_fetch(pc);

		//stage 2 of the pipeline
		pipeline_advance = instruction_ready & ! mem_is_busy;

		//instruction_decode and register_fetch
		instruction_decode(next_instruction, flags, pipeline_advance);
		branch_unit(return_addr, return_en, br_unit_en, imm11, pipeline_advance);

		//execute the instruction
		alu(contents_a, contents_b, imm7, opcode);
		mem_access_unit(contents_a, contents_b, load_n_store, ld_st_comming);
		condition_unit(new_flags, set_cond_en);

		//write back
		write_back(alu_en, alu_result, load_n_store, load_result, bsr_en, new_return_addr);
	    }	
		
	System.out.println("\nRegister contents...");
	for(int i=register_file.length -1; i>=0; i--)
	    if(reg_val_defined[i])
		System.out.println("r" + i + " : " + register_file[i]);
	    else
		System.out.println("r" + i + " : x");
	
    }

    static void init() //initialises the instruction memory - ignore for your own sanity!!!
    {
	char [] instruction_buffer = new char [1<<20];
	int [] int_buffer = new int [4];
	try
	    {
		BufferedReader in = new BufferedReader(new FileReader("..\\N_105_system_sim\\onchip_rom_lane0.dat"));
		//this file is the same one read byt the hardware simulation and generated by the assembler (probably!)
		//char [] stuff = new char [15]; //crap at the beginning of the file
		//in.read(stuff,0,15);
		
		int file_length = in.read(instruction_buffer,0,instruction_buffer.length);
		//System.out.println(instruction_buffer);
		int k = 10;
		for(int i = 0; i<(file_length/4); i++)
		    {
			for(int j = 0; (j<4 && k<file_length); k++)
			    {
				int_buffer[j] = (int)instruction_buffer[k];
				
				if (int_buffer[j] < 58)
				    int_buffer[j] -= 48;
				else
				    int_buffer[j] -= 87;

				if(int_buffer[j] >= 0 && int_buffer[j] < 16)    
				    j++;
			    }
			instruction_mem[i] = (char)((int_buffer[0] << 12) + (int_buffer[1] << 8) + (int_buffer[2] << 4) + (int_buffer[3]));
			program_length = i;
			//System.out.println((int)instruction_mem[i]);
		    }
		in.close();
	    }
	catch(Exception e)
	    {
		System.out.println(e);
	    }
	for(int i = 0; i<16; i++)
	    reg_val_defined[i] = false;
    }

	//You can start reading again now....


    //instruction decode decodes the instrucion and updates all the control flow signals
    //it also executes "ifs" conditional statements

    static void instruction_decode(short the_next_instruction, boolean [] flags, boolean pipeline_advance)
    {
	if(pipeline_advance)
	    instruction.write((char)next_instruction);

	//divide the instruction into its relevent parts
	index_a = instruction.subscript(15,12).getByte();
	index_b = instruction.subscript(11,8).getByte();
	imm4 = index_b; 
	imm7_signed = instruction.subscript(11,5).getShort();
	imm7_unsigned = instruction.subscript(11,5).getChar();
	imm11 = instruction.subscript(15,5).getShort();
      	opcode = instruction.subscript(4,0).getByte();

	char next_opcode = (char)(next_instruction & ((1<<5) - 1)); 
	//set the enable signals
	ld_st_comming = (next_opcode == LD || next_opcode == ST) && !skip;
	alu_en = (!skip && (instruction.getBit(4) == false) && (opcode != CMP) && (opcode != CMPI));
	load_n_store = (!skip && opcode == LD);
	bsr_en = (!skip && opcode == BSR);
	return_en = (!skip && opcode == RET);
	br_unit_en = (!skip && (opcode == BR || opcode == BSR || opcode == RET));
	set_cond_en = (!skip && (opcode == OR || opcode == AND ||opcode == XOR ||
				 opcode == ADD || opcode == SUB ||opcode == CMP ||
				 opcode == ADDI ||opcode == SUBI ||opcode == CMPI));

	//fetch the register values
	register_fetch(index_a, index_b, bsr_en, return_en, dest_contents, write_en, pipeline_advance);

	//ifs - deal with if statements
	boolean condition;
	switch(imm4 >> 1)
	    {
		case 0: condition = flags[C];
		break;
		case 1: condition = flags[Z];
		break;
		case 2: condition = flags[N];
		break;
		case 3: condition = !(flags[N]^flags[V]);
		break;
		case 4: condition = flags[Z]|(flags[N]^flags[V]);
		break;
		case 5: condition = flags[V];
		break;
		case 6: condition = flags[C]|flags[Z];
		break;
		case 7: condition = false;	
	    default: condition = false;
	    }

	if(opcode == IFS)
	    {
		if((byte)(imm4 << 7) == 0)
		    skip = condition;
		else
		    skip = !condition;
	    }
	else
	    skip = false;
	System.out.print(" " + skip);
    }
    
    //alu = arithmetic logic unit : executes arithmetic instructions and calculates the condition flags
    static void alu(short contents_a, short contents_b, byte imm7, byte opcode)
    {
	int result_wc = 0; //result with carry

	new_flags[N] = false; //init flags
	new_flags[V] = false;
	new_flags[Z] = false;
	new_flags[C] = false;

	//Shifting is a lot more compilcated in verilog so there is a separate shifter module instantiated by the alu
	//I have not included it here because shifting is so simple in Java
	//It would be ...
	//shifter(opcode[1:0], imm4, contents_a) returning shifted_result

	switch(opcode)
	    {
	    case OR: alu_result = (short)(contents_a | contents_b);
		break;
	    case AND: alu_result = (short)(contents_a & contents_b);
		break;
	    case XOR: alu_result = (short)(contents_a ^ contents_b);
		break;
	    case NOT: alu_result = (short)(~contents_a);
		break;

	    case MOV: alu_result = contents_b;
		break;
	    case ADD: result_wc =((int)contents_a & 0xffff) + ((int)contents_b & 0xffff);
		alu_result = (short)result_wc;
		new_flags[V] = ((contents_a < 0) == (contents_b < 0)) && ((contents_a < 0) != (alu_result <0));	
		new_flags[C] = ((result_wc >> 16) == 1);
		break;
	    case SUB: result_wc = ((int)contents_a & 0xffff) - ((int)contents_b & 0xffff);
		alu_result = (short)result_wc;
		new_flags[V] = ((contents_a < 0) != (contents_b < 0)) && ((contents_a < 0) != (alu_result <0));
		new_flags[C] = (char)contents_a < (char)alu_result ;
		break;
	    case CMP: result_wc = ((int)contents_a & 0xffff) - ((int)contents_b & 0xffff);
		alu_result = (short)result_wc;
		new_flags[V] = ((contents_a < 0) ^ (contents_b < 0)) && ((contents_a < 0) ^ (alu_result <0));
		new_flags[C] = (char)contents_a < (char)alu_result ;
		break;

	    case LSLI: alu_result = (imm4 == 0) ? contents_a :
		(short)(contents_a << (16-imm4));
		break;
	    case LSRI: alu_result = (short)(((int)contents_a & 0xffff) >>> imm4);
		break;
	    case ASRI: alu_result = (short)(contents_a >> imm4);
		break;
	    case ROTI: alu_result = (imm4 == 0) ? contents_a :
		(short)(((((int)contents_a  << 16) | (contents_a & 0xffff)) << (16-imm4)) >> 16);
		break;
  
				      
	    case MOVI: alu_result = imm7_signed;
		break;
	    case ADDI: result_wc = ((int)contents_a & 0xffff) + (int)imm7_unsigned;
		alu_result = (short)result_wc;
		new_flags[V] = (contents_a > 0) && (alu_result < 0); 
		new_flags[C] = ((result_wc >> 16) == 1);
		break;
	    case SUBI: result_wc = ((int)contents_a & 0xffff) - (int)imm7_unsigned;
		alu_result = (short)result_wc;
		new_flags[V] = (contents_a < 0) && (alu_result > 0);
		new_flags[C] = (char)contents_a < (char)alu_result ;
		break;
	    case CMPI: result_wc = ((int)contents_a & 0xffff) - (int)imm7_signed;
		alu_result = (short)result_wc;
		new_flags[V] = (contents_a < 0) && (alu_result > 0) && (imm7_signed > 0);
		new_flags[C] = (char)contents_a < (char)alu_result ;
				
	    default:;
	    }
	//set the rest of the flags
	new_flags[N] = alu_result < 0;
	new_flags[Z] = (alu_result == 0);
    }

	//calculates the new pc after a branch/return/branch to subroutine
    static void branch_unit(short return_addr, boolean return_en, boolean br_unit_en, short imm11, boolean pipeline_advance)
    {
	new_return_addr = (short)(pc + 1);
	 
	    if(pipeline_advance)
	    {
		if(br_unit_en)
		    if(return_en)
			pc = return_addr;
		    else
			pc = (short)(pc + imm11);
		else
		    pc = (short)(pc + 1);
	    }
    }
    
    //executes loads and stores
    static void mem_access_unit(short contents_a, short contents_b, boolean load_n_store, boolean ld_st_comming)
    {
	//here I have ignored the interface to avalon bus. For more information see the Avalon bus documentation.
	//the Avalon bus provides an interface to the data memory

	mem_is_busy = ld_st_comming;
	if (mem_is_busy)
	    {
		if (load_n_store)
		    load_result = (short)data_mem[contents_b];
		else 
		    data_mem[contents_b] = (char)contents_a;

		mem_is_busy = false;
	    }
    }

    //handles the condition flags
    static void condition_unit(boolean [] new_flags, boolean set_cond_en)
    {
	if (set_cond_en)  //update flags
	    {
		flags[N] = new_flags[N];
		flags[V] = new_flags[V];
		flags[Z] = new_flags[Z];
		flags[C] = new_flags[C];
	    }
    }

    //mutliplexes results to find out what should be written back
    static void write_back(boolean alu_en, short alu_result, boolean load_en, short load_result, boolean bsr_en, short new_return_addr)
    {
	write_en = (alu_en || load_en || bsr_en);

	if (alu_en)
	    dest_contents = alu_result;
	if (load_en)	    
	    	dest_contents = load_result;
	if (bsr_en)
	    dest_contents = new_return_addr; //r15
       
	if(alu_en || load_en)
	  System.out.print("write back " + dest_contents + " to r" + index_a);
	else if(bsr_en)
	   System.out.print("write back " + dest_contents + " to r15");
	    
	register_fetch(index_a, index_b, bsr_en, return_en, dest_contents, write_en, pipeline_advance);

	write_en = false;
    }

    //reads from and writes to the register file
    static void register_fetch(byte index_a, byte index_b, boolean bsr_en, boolean ret_en, short dest_contents, boolean write_en, boolean read_en)
    {
	if (read_en)
	    {
		if (ret_en)
		    contents_a = register_file[15];
		else
		    contents_a = register_file[index_a];
		contents_b = register_file[index_b];
	    }
	if (write_en)
	    {
		if (bsr_en)
		    {
			register_file[15] = dest_contents;
			reg_val_defined[15] = true;
		    }
		else
		    {
			register_file[index_a] = dest_contents;
			reg_val_defined[index_a] = true;
		    }
	    }
    }

    //fetches instructions from the instruction memory
    static void instruction_fetch(short pc)
    {
	//I have ignored the interface to Avalon bus.
	//The Avalon bus provides an interface to the instruction memory 

	next_instruction = (short)instruction_mem[pc];
	
    }
}































