`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: Jürgen Böhm
// 
// Create Date:    02:08:39 02/18/2007 
// Design Name: 
// Module Name:    cpu 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////


module comp_lt ( a, b, is_lt );

input[31:0] a,b;
output reg is_lt;

always @(a,b)
	begin
		if (a[31]==b[31])
			is_lt <= (a[30:0] < b[30:0]);
		else
			is_lt <= a[31];		
	end

endmodule


module alu ( opcodeIn, opAin, opBin, opCin, aluOut, overflow, carry, zero, freeze, clk );

input[31:0] opAin, opBin, opCin;
input[5:0] opcodeIn;

output[31:0] aluOut;

output overflow, carry, zero;

input freeze;

input clk;

reg[31:0] aluOut;

assign zero = (aluOut == 0);


// experiment in staging the ALU:


/*
reg[31:0] opA, opB, opC;
reg[5:0] opcode;

always @(posedge clk)
	begin
		opA <= opAin;
		opB <= opBin;
		opC <= opCin;
		opcode <= opcodeIn;
	end

*/
wire[31:0] opA = opAin;
wire[31:0] opB = opBin;
wire[31:0] opC = opCin;

wire[5:0] opcode = opcodeIn;


// the comparators

wire aLTb;
wire aGTb;

comp_lt comp_lt0 (opA, opB, aLTb );
//comp_lt comp_lt1 (opB, opA, aGTb );


wire aEQb;
wire aLTEb;
wire aGTEb;

assign aGTb = ~(aEQb | aLTb);

assign aEQb = (opA == opB);
assign aLTEb = aLTb | aEQb;
assign aGTEb = aGTb | aEQb;


// the main alu

wire[31:0] lenVect = 32'd12 + {opA[29:0], 2'b0};

always @(posedge clk)
	begin
		if (~freeze)
			begin
				case (opcode)
					0: aluOut <= {opA[31:2] + opB[31:2],2'b0};
					1: aluOut <= {opA[31:2] - opB[31:2],2'b0};

					2: aluOut <= aLTEb ? 4 : opC;
					3: aluOut <= aGTEb ? 4 : opC;
					4: aluOut <= aLTb ? 4 : opC;
					5: aluOut <= aGTb ? 4 : opC;
					6: aluOut <= aEQb ? 4: opC;

					11: aluOut <= opA | opB;
					12: aluOut <= opA & opB;
		//			13: aluOut <= ~(opA | opB);
		
					// barrel shift operations (costly in space)
		
					14: aluOut <= (opA >> (opB[6:2]));
					15: aluOut <= (opA << (opB[6:2]));
						


					16: aluOut <= {opA[31:2], opA[1:0] & opB[3:2]};
					17: aluOut <= {opA[31:2], opA[1:0] | opB[3:2]};
					18: aluOut <= {opA[31:2], opA[1:0] ^ opB[3:2]};
					19: aluOut <= (opA[1:0] == opB[3:2]) ? 4 : opC;
					20: aluOut <= {28'b0,opB[1:0],2'b0};
					21: aluOut <= {opA[31:2],opB[3:2]};

					24,25: aluOut <= (opA[31:2] == opB[31:2]) ? 4 : opC;
					26: aluOut <= {opA[31:2] & opB[31:2], opA[1:0]};
					27: aluOut <= {opA[31:2] | opB[31:2], opA[1:0]};
					28: aluOut <= {opB[31:2],2'b0};


					32: if (|(opB[2:0]))
								begin
									aluOut <= {opB[31:3],3'b0} + 32'd8;
								end
							else
								begin
									aluOut <= opB;
								end

					33: aluOut <= lenVect;
					34: aluOut <= {opB[31:2],2'b10};
					35: aluOut <= {opB[31:2],2'b00} + {opA[31:2],2'b00};
					36: aluOut <= opA + {2'b0, opB[31:2]};

					37: aluOut <= {opA[17:2],opB[17:2]};
					38: aluOut <= {opA[31:2],2'b00} + {opB[31],opB[31],opB[31:2]};
//					39: aluOut <= {opB[30:0],1'b0};
//					40: aluOut <= {1'b0,opB[31:1]};
					default : aluOut <= opB;
				endcase
			end
		else
			begin
				aluOut <= aluOut;
			end
	end

endmodule




module cpu(	addr,
				datain,
				dataout,
				readmem,
				writemem,
				waitrq,
				irq,
				type,
				rst,
				clk,
				tosout,
				debugout
				);

output[31:0] tosout;
reg[31:0] tosout;

output[31:0] debugout;
reg[31:0] debugout;


output[31:0] addr;

input[31:0] datain;
output[31:0] dataout;

output readmem;
output writemem;

input waitrq;

wire waitrq1;

input[2:0] irq;

output[1:0] type;


input rst;
input clk;

//registered output lines


reg readmem;
reg writemem;

reg[1:0] type;

// special provision for data in/out

reg[31:0] datao;
reg dataeno;



// registers


reg[31:0] addro;

assign addr = addro;

reg[31:0] csp, dsp;  // code stack pointer , data stack pointer

reg[31:0] ir; 

reg[31:0] pc;

wire[31:0] datamem; // data input buffer;

wire[31:0] datamem2 = {datamem[29:0],2'b0};

reg[31:0] litbase;

reg[31:0] template;


// regA to regE


reg[31:0] regA;
reg[31:0] regB;

reg[31:0] regC;


reg[31:0] regD;

reg[31:0] regE;


reg[31:0] tos;


// status word

reg[31:0] status;


// irq mask

wire[2:0] irq_akt;

assign irq_akt = status[5:3];

wire irq_enabled;

assign irq_enabled = status[2];



// right shifted regA


wire[31:0] regA2 = {2'b0,regA[31:2]};


// the ALU

wire[31:0] aluOut;

wire[5:0] aluOp;

wire overflow, carry, zero;

wire[31:0] opA;

reg[31:0] aluOpA;

alu alu0 (.opcodeIn(aluOp),.opAin(aluOpA),.opBin(tos),.opCin(regC),
									.aluOut(aluOut),.overflow(overflow),.carry(carry),.zero(zero),
									.freeze(waitrq1),.clk(clk));


wire[5:0] aluOp_h;

wire use_aluOp_h;

assign aluOp = use_aluOp_h ? aluOp_h : ir[29:24];

wire is_nil = (tos == regC);

// the bus structures and multiplexors




// data bus




// derived signal d from ir

wire[31:0] d;
wire[15:0] d1;

assign d1 = ir[15:0];

assign d = {{14{d1[15]}},d1,1'b0,1'b0};

wire[31:0] litbase_plus_d = litbase + d;



wire[31:0] litbase_addr = template + 32'd16;

wire[31:0] tp_4 = template + 32'd4;

wire[31:0] pc_4 = pc + 32'd4;

wire[31:0] regA_8 = regA  + 32'd8;


// some constants:


`define vect_header_tag 32'd5
`define clos_header_tag 32'd13
`define val_3 32'd3

`define val_4 32'd4
`define val_8 32'd8
`define val_12 32'd12
`define val_16 32'd16



// aluOpA

wire[2:0] alu_source;


always @(alu_source, datamem, regA, regB, dsp)
	begin
		case (alu_source)
			0: aluOpA <= datamem;
			1: aluOpA <= regA;
			2: aluOpA <= regB;
			3: aluOpA <= `val_8;
			4: aluOpA <= `val_12;
			5: aluOpA <= `val_16;
			6: aluOpA <= `val_3;
			7: aluOpA <= dsp - 32'd4;
			default: aluOpA <= regA;
		endcase
	end



// dat


reg[31:0] datbus;

assign opA = datbus;

wire[4:0] dat_source;


always @(regA, regB, regC, regD, regE, litbase, datamem, aluOut, dat_source, tos,
					status, pc, dsp, csp,
					litbase_addr, template, pc_4, d, datamem2, regA2)
	begin
		case (dat_source)
		0: begin datbus <= datamem; end
		1: begin datbus <= aluOut; end
		2: begin datbus <= regA; end
		3: begin datbus <= regB; end
		4: begin datbus <= regC; end
		5: begin datbus <= regD; end
		6: begin datbus <= litbase; end
		7: begin datbus <= tos; end
		8: begin datbus <= litbase_addr; end
		9: begin datbus <= template; end
		10: begin datbus <= regE; end
		11: begin datbus <= pc_4; end
		12: begin datbus <= d; end
		13: begin datbus <= datamem2; end
		14: begin datbus <= regA2; end
		15: begin datbus <= `val_4; end
		16: begin datbus <= `val_8; end
		17: begin datbus <= `val_12; end
		18: begin datbus <= `val_16; end
		19: begin datbus <= `val_3; end
		20: begin datbus <= `vect_header_tag; end
		21: begin datbus <= `clos_header_tag; end
		22: begin datbus <= status; end
		23: begin datbus <= 32'h0; end
		24: begin datbus <= pc; end
		25: begin datbus <= dsp; end
		26: begin datbus <= csp; end
		default: begin datbus <= regA; end
		endcase
	end
	

wire[4:0] dat_dest;

reg[31:0] dummy;

always @(posedge clk)
	begin
		case (dat_dest)
			1: ir <= datbus;
//			2: status <= datbus; implemented further below
			3: tos <= datbus;
			4: regA <= datbus;
			5: regB <= datbus;
			6: regC <= datbus;
			7: regD <= datbus;
			8: litbase <= datbus;
			9: template <= datbus;
			10: regA <= regA + 4;
			11: regE <= datbus;
			12: tos <= tos + 4;
			13: tos <= tos - 4;
			14: regB <= regB + 4;
			default: dummy <= datbus;
		endcase
	end


// data bus interface control

assign datamem = ~dataeno ? datain : 32'bz;
assign dataout = dataeno ? datbus : 32'bz;




// addr


wire[4:0] addr_source;

`define niladdr 32'h0000


// ab hier wird experimentiert...

reg[31:0] asa1;
reg[31:0] asa2;
reg[31:0] asa3;

wire[31:0] asasum = asa1 + asa2 + asa3;


always @(addr_source, csp, dsp)
	begin
		case (addr_source)
			1,2,3, 15: asa1 <= dsp;
			5,6,7: asa1 <= csp;
			default: asa1 <= 0;
		endcase
	end
	
always @(addr_source, d)
	begin
		case (addr_source)
			1, 5: asa2 <= 32'd4;
			2, 6: asa2 <= 32'hfffffffc;
			3, 7: asa2 <= d;			
//			15: asa2 <= d - 32'd4;
			15: asa2 <= d;
			default: asa2 <= 0;
		endcase
	end

always @(addr_source)
	begin
		case (addr_source)
			1,5, 2,6, 3, 7: asa3 <= 0;
			15: asa3 <= 32'hfffffffc;
			default: asa3 <= 0;
		endcase
	end


// hier gehts normal weiter

always @(asasum, dsp, csp,  pc, aluOut, litbase, regA, regB, regD, addr_source,
			litbase_plus_d, litbase_addr, tos, regA_8, tp_4)
	begin
		case (addr_source)
			5'd0: addro <= dsp;
			5'd1: addro <= asasum;
			5'd2: addro <= asasum;
			5'd3: addro <= asasum;
			5'd4: addro <= csp;
			5'd5: addro <= asasum;
			5'd6: addro <= asasum;
			5'd7: addro <= asasum;
			5'd8: addro <= pc;
			5'd9: addro <= aluOut;
			5'd10: addro <= regA;
			5'd11: addro <= regB;
			5'd12: addro <= regD;
			5'd13: addro <= litbase_plus_d;
			5'd14: addro <= litbase_addr;
			5'd15: addro <= asasum;
			5'd16: addro <= tos;
			5'd17: addro <= regA_8;
			5'd18: addro <= tp_4;
			5'd29: addro <= 32'h40;
			5'd30: addro <= `niladdr;
			default: addro <= 32'bz;
		endcase
	end




// cdsp

wire[2:0] cdsp_dest;

wire[2:0] cdsp_source;

reg[31:0] cdspbus;

// ab hier wird experimentiert...

reg[31:0] csa1;
reg[31:0] csa2;


wire[31:0] csasum = csa1 + csa2;

always @(csasum)
	begin
		cdspbus <= csasum;
	end

always @(cdsp_source, csp, dsp)
	begin
		case (cdsp_source)
			0,2,4,6: csa1 <= csp;
			1,3,5,7: csa1 <= dsp;
		endcase
	end
	

	
always @(cdsp_source, d)
	begin
		case (cdsp_source)
			0,1: csa2 <= d;
			2,3: csa2 <= -d;
			4,5: csa2 <= 32'd4;
			6,7: csa2 <= 32'hfffffffc;
		endcase
	end


// auxiliary initialization counter


reg[3:0] auxcnt;

wire rst_long;

assign rst_long = |auxcnt;

always @(posedge clk or posedge rst)
	begin
		if (rst)
			begin
				auxcnt <= 4'd15;
			end
		else
			begin
				if (rst_long)
					auxcnt <= auxcnt - 1;
			end
	end



always @(posedge clk)
	if (rst_long)
		begin
			csp <= 32'h0500;
			dsp <= 32'h0800;
		end
	else
	begin
		case (cdsp_dest)
			3'd0: csp <= cdspbus;
			3'd1: dsp <= cdspbus;
			3'd2: csp <= datbus;
			3'd3: dsp <= datbus;
			default: begin
							dsp <= dsp;
							csp <= csp;
						end
		endcase
	end
	
// pcb

wire[2:0] pcb_source;

always @(posedge clk)
	begin
			case (pcb_source)
				3'd0: pc <= pc_4;
				3'd1: pc <= pc + d;
				3'd2: pc <= datbus;
				3'd3: begin
								if (is_nil)
									begin
										pc <= pc + d;
									end
								else
									begin
										pc <= pc_4;
									end
				end
				default: pc <= pc;
			endcase
	end



// control ir read

reg irRead;




// miscellaneous

always @(dataeno)
	begin
		writemem <= dataeno;
	end
	

// micro program control

wire[31:0] mic_inst_raw;
wire[31:0] mic_inst_h_raw;


reg[31:0] mic_inst;
reg[31:0] mic_inst_h;

reg[9:0] micr_pc;
reg[9:0] micr_pc_new;


wire[9:0] next_micr_pc;

assign next_micr_pc = micr_pc + 1;


assign addr_source = mic_inst[31:27];
assign dat_source = mic_inst[26:22];
assign dat_dest = mic_inst[21:17];
assign cdsp_source = mic_inst[16:14];
assign cdsp_dest = mic_inst[13:11];
assign pcb_source = mic_inst[10:8];

wire data_eno_mic = mic_inst[6:6];

wire read_mem = mic_inst[7:7];

wire[3:0] mic_next = mic_inst[3:0];

wire dispatch = mic_inst_h[6:6];

wire to_page_zero = mic_inst_h[7:7];

wire[1:0] mtyp = mic_inst_h[9:8];

assign aluOp_h = mic_inst_h[5:0];

assign use_aluOp_h = mic_inst_h[10:10];

assign alu_source = mic_inst_h[13:11];

wire[1:0] start_interrupt;

assign start_interrupt = mic_inst_h[15:14];


always @(mtyp)
	begin
		type <= mtyp;
	end

always @(read_mem)
	begin
		readmem <= read_mem;
	end



always @(data_eno_mic)
	begin
		dataeno <= data_eno_mic;
	end



wire irq_pending = (irq > irq_akt);

wire cycle_start = (micr_pc == 10'b0);

wire irq_cycle_start = cycle_start && irq_enabled && irq_pending && (~|auxcnt);



always @(posedge clk)
	begin
		if (rst_long)
			begin
				status = 32'h4; // start with interrupts enabled (status[2] = 1)
			end
		else
			begin
				if (dat_dest == 2)
					begin
						$display ( "assign status, val is = %h", datbus );
						status = datbus;
					end
				else if (start_interrupt == 2'b01)
					begin
						$display ( "setting priority, new is = %h", irq );
						status[8:6] = status[5:3];
						status[5:3] = irq;
						status[2] = 1'b0; // irq disabled
					end
				else if ((start_interrupt == 2'b10) & status[9]) 
										// status[9] is the switch that transforms an ordinary RET into a
										// RET from interrupt
					begin
						$display ( "resetting priority, new is = %h", status[8:6] );
						status[9] = 1'b0;
						status[5:3] = status[8:6];
						status[2] = 1'b1; // irq reenabled
					end
				else if ((start_interrupt == 2'b10))
					begin
//						$display ( "passing RET without resetInt" );
//						$stop;
					end
			end
	end


assign waitrq1 = waitrq & cycle_start;


always @(mic_next, datamem, dispatch, waitrq1, micr_pc, micr_pc_new, rst_long, ir, tos, to_page_zero, irq_cycle_start)
	begin
//		if (rst | |auxcnt) begin
		if (rst_long) begin
			micr_pc_new <= 62* 8;
			tosout <= 0;
		end else begin
			if (~waitrq1)
				begin
//					$display ( "micr_pc = %h, ir = %h, pc is = %h, data is = %h, addr = %h, csp = %h, cdsp_dest = %h readmem = %h, writemem = %h", 
//							micr_pc, ir, pc, data, addr, csp, cdsp_dest, readmem, writemem );
					debugout <= {16'b0,ir[31:16]};
					if (ir[23:16] == 8'd60)
						tosout <= tos;
					if (to_page_zero)
						begin
							micr_pc_new <= {6'b0, mic_next};
						end
					else if (irq_cycle_start)
						begin
							$display ( "irq cycle start.");
							micr_pc_new <= {1'b0, 6'd14, 3'b0};
						end
					else if (dispatch)
						begin
							micr_pc_new <= {1'b0,datamem[21:16],mic_next[2:0]};
						end
					else
						begin
							micr_pc_new <= {micr_pc[9:4],mic_next};
						end
				end
			else
				begin
					micr_pc_new <= micr_pc;
				end
		end
	end


always @(posedge clk) // or posedge rst)
	begin
//		if (rst | |auxcnt)
		if (rst_long)
			begin
//				$display ( "cpu rst = %h", rst );
				micr_pc <= 62* 8;
			end
		else
			begin
/*			
				if ((micr_pc_new[8:3] == 14) || (micr_pc_new[8:3] == 15))
					begin
						$display ("in irq : micr_pc_new = %h", micr_pc_new );
					end
				if ((micr_pc_new[8:3] == 42) || (micr_pc_new[8:3] == 41))
					begin
						$display ("get-set-status ir = %h status = %h", ir, status );
					end
*/
				micr_pc <= micr_pc_new;
			end
		
	end



`define mic_inst_wait {5'd31, 5'd7, 5'd31, 3'd7, 3'd4, 3'd4, 1'd0, 1'd0, 2'd0, 4'd0}
`define mic_inst_wait_h {18'd0, 3'd1, 1'd0, 2'd0, 1'd0, 1'd0, 6'd63}


always @(waitrq1, rst_long, mic_inst_raw, mic_inst_h_raw)
	begin
		if (~waitrq1 & ~rst_long)
			begin
				mic_inst <= mic_inst_raw;
				mic_inst_h <= mic_inst_h_raw;
			end
		else
			begin
				mic_inst <= `mic_inst_wait;
				mic_inst_h <= `mic_inst_wait_h;
			end
	end
	


micro_store mcs0 ( .address({micr_pc_new[9:0]}), .data(mic_inst_raw), .read_en(1'b1), .ce(1'b1), .clk(clk)); 
micro_store_1 mcs1 ( .address({micr_pc_new[9:0]}), .data(mic_inst_h_raw), .read_en(1'b1), .ce(1'b1), .clk(clk));


endmodule

