`define elev 2
`define floor 3
`define width 2
typedef enum {UP,DOWN} dir;
typedef enum {STOPPED,MOVING} mov;
typedef enum {OPEN,OPENING,CLOSED,CLOSING} dr;
typedef enum {ON,OFF} onoff;

//***********************************************  
module main(clk);
input clk;
wire[1:`elev] stop_next;
wire[1:`elev] inc;
wire[1:`elev] dec;
wire[1:`elev] continue;
wire[0:`width-1] init1, init2, init11, init22;

assign init11=$ND(0,1,2,3);
assign init22=$ND(0,1,2,3);
assign init1 = init11==3?2:init11;
assign init2 = init22==3?2:init22;

elevator e1(clk,stop_next[1],inc[1],dec[1],continue[1],init1);
elevator e2(clk,stop_next[2],inc[2],dec[2],continue[2],init2);
main_control main_control(clk,inc,dec,stop_next,continue,init1,init2);
endmodule
//***********************************************  

//************************************************
module main_control(clk,inc,dec,stop_next,continue,init1,init2);
input clk,inc,dec,init1,init2;
output stop_next,continue;
wire[1:`elev] inc;
wire[1:`elev] continue;
wire[1:`elev] dec;
wire[1:`elev] stop_next;
reg [0:`width-1] locations[1:`elev];
onoff reg up_floor_buttons[0:`floor-1];
onoff reg down_floor_buttons[0:`floor-1];
wire[0:`floor-1] random_up;
wire[0:`width-1] init1,init2;
wire[0:`floor-1] random_down;
wire[0:`floor-1] buttons;
wire[1:`elev] button_above,button_below;
dir reg direction[1:`elev];
initial begin
	 locations[1]=init1;
	 locations[2]=init2;
	 up_floor_buttons[0]=OFF;
	 up_floor_buttons[1]=OFF;
	 up_floor_buttons[2]=OFF;
	 down_floor_buttons[0]=OFF;
	 down_floor_buttons[1]=OFF;
	 down_floor_buttons[2]=OFF;
	 direction[1]=UP;
	 direction[2]=UP;
end

//compute if elevator should continue in same direction. We skip the next floor assuming
// that the stop_next computation will take care of this.
assign buttons[0] = up_floor_buttons[0]==ON || down_floor_buttons[0]==ON;
assign buttons[1] = up_floor_buttons[1]==ON || down_floor_buttons[1]==ON;
assign buttons[2] = up_floor_buttons[2]==ON || down_floor_buttons[2]==ON;
assign button_below[1] = 
	((locations[1]==2)&&(buttons[0]||buttons[1]))
        || (locations[1]==1 && buttons[0]);
assign button_above[1] = ((locations[1]==0)&&(buttons[2]||buttons[1]))
        || ((locations[1]==1)&&(buttons[2]));
assign button_below[2] = 
        ((locations[2]==2)&&(buttons[0]||buttons[1]))
        || (locations[2]==1 && buttons[0]);
assign button_above[2] = ((locations[2]==0)&&(buttons[2]||buttons[1]))
        || ((locations[2]==1)&&(buttons[2]));
assign continue[1] = button_above[1] && direction[1]==UP || button_below[1] && direction[1]==DOWN;
assign continue[2] = button_above[2] && direction[2]==UP || button_below[2] && direction[2]==DOWN;

//schedule the next pickup
	assign stop_next[1]=((locations[1] != `floor-1)&&(direction[1]==UP))?
		((up_floor_buttons[locations[1]+1]==ON)?1:0):
                (((locations[1] != 0)&&(direction[1]==DOWN))?
                ((down_floor_buttons[locations[1]-1]==ON)?1:0):0);
        assign stop_next[2]=((locations[2] != `floor-1)&&(direction[2]==UP))?
                ((up_floor_buttons[locations[2]+1]==ON)?1:0):
                (((locations[2] != 0)&&(direction[2]==DOWN))?
                ((down_floor_buttons[locations[2]-1]==ON)?1:0):0);
	assign random_up[0] = $ND(0,1);
	assign random_down[0] = $ND(0,1);
	assign random_up[1] = $ND(0,1);
	assign random_down[1] = $ND(0,1);
	assign random_up[2] = $ND(0,1);
	assign random_down[2] = $ND(0,1);


always@(posedge clk) begin
// randomly push floor buttons
for (i=0;i<=`floor-1;i=i+1)begin
	if (random_up[i]) up_floor_buttons[i]=ON;
	if (random_down[i]) down_floor_buttons[i]=ON;
end

//turn off scheduled floor buttons.
// it is important to turn these off after the random pushes, since we
// want the scheduled buttons to be OFF even though they may have been 
// randomly pushed.
for (i=1;i<=`elev;i=i+1) begin
	if ((locations[i] != `floor-1)&& (direction[i] == UP)) begin
	if (up_floor_buttons[locations[i]+1]==ON) begin
		up_floor_buttons[locations[i]+1] = OFF;
		end
	end
	if ((locations[i] != 0)&& (direction[i] == DOWN)) begin
	if (down_floor_buttons[locations[i]-1]==ON) begin
		down_floor_buttons[locations[i]-1] = OFF;
		end
	end
end
end

//keep track of locations and directions
always@(posedge clk) begin
for (i=1;i<=`elev;i=i+1) begin
	if (locations[i]==`floor-1) direction[i] = DOWN;
	if (locations[i]==0) direction[i]=UP;
	if(inc[i]) begin
		locations[i]=locations[i]+1;
		direction[i]=UP;
		end
	if(dec[i]) begin
		locations[i]=locations[i]-1;
		direction[i]=DOWN;
		end
end
end

endmodule
//***********************************************  

//***********************************************  
module elevator(clk,stop_next,inc,dec,continue,init);
input clk,stop_next,continue,init;
output inc,dec;
onoff reg buttons[0:`floor-1];
wire [0:`width-1] init;
reg[0:`width-1] location;
dir reg direction;
mov reg movement;
dr reg door;
reg open_next;
wire button_above, button_below;

//initial begin
initial	open_next = 0;
initial	location = init;
initial	direction = UP;
initial	door = OPEN;
initial	movement = STOPPED;
initial	buttons[0]=OFF;
initial	buttons[1]=OFF;
initial	buttons[2]=OFF;
//end

wire[0:`floor-1] random_push;
wire button_above,button_below;
assign random_push = $ND(0,1,2,3,4,5,6,7);
assign button_below = 
		((location==2)&&(buttons[1]==ON||buttons[0]==ON)) ||
		((location==1)&&buttons[0]==ON);
assign button_above = ((location==0)&&(buttons[2]==ON||
			buttons[1]==ON)) ||
			((location==1)&&(buttons[2]==ON));

//******************************
always@(posedge clk) begin
// randomly push buttons. 
// But when door is open turn button off for that floor. 
for (i=0;i<=`floor-1;i=i+1) begin
	if (i == location) buttons[i]=OFF;
	else if (random_push[i]) buttons[i]=ON;
end

// record a request to stop at the next floor
// it is important that this happens last since we want to
// insure that the stop_next request is always recorded by
// pushing the button.
if(stop_next) begin
	if (direction==UP) buttons[location+1]=ON;
	else buttons[location-1]=ON;
	end
end
//*******************************

//*******************************
always@(posedge clk) begin
//schedule the door to open at the next floor
if(door != CLOSED) open_next=0;
else if (movement==MOVING&&(stop_next||(direction == UP&&buttons[location+1]==ON)||
				(direction == DOWN&&buttons[location-1]==ON)))
		open_next=1;
end
//*******************************

wire random;
assign random = $ND(0,1);

//*******************************
always@(posedge clk) begin
//Door operation: open the door if button[location] is on.
//Random pause between different states.
	case (door)
		CLOSED: if (open_next&&movement==STOPPED)
			door=OPENING;
		OPENING: if (random) door = OPEN;
		OPEN: if (random) door = CLOSING;
		CLOSING: if (random) door = CLOSED;
		endcase
end
//*******************************

// Move to next floor. Increase or decrease location when arrived.
// Signal to main control (through inc or dec) that have arrived at next floor.
wire stop_moving;
wire start_moving;
wire r_stop;
assign start_moving = (continue || button_above&&direction==UP) || 
			(button_below && direction == DOWN);
assign r_stop = $ND(0,1);
assign stop_moving = r_stop&&(movement == MOVING);
assign inc = (stop_moving)&&(direction == UP);
assign dec = (stop_moving)&&(direction == DOWN);
//*******************************
always@(posedge clk) begin
if (door == CLOSED) begin
	case (movement)
		STOPPED: if (door==CLOSED&&start_moving&&!open_next) 
			movement=MOVING;
		MOVING: if (stop_moving) begin
			movement=STOPPED;
			if (direction == UP) location = location+1;
			if (direction == DOWN) location = location-1;
			end
		endcase
	end
end
//*******************************
		
// Determine direction of movement

//*******************************
always@(posedge clk) begin
	case (direction) 
		UP: if((!button_above)&&!continue) 
			direction = DOWN;
		DOWN: if((!button_below)&&!continue) 
			direction = UP;
		endcase
	if(location==`floor-1) direction=DOWN;
	if(location==0) direction=UP;
	end
//*******************************

endmodule
//***********************************************  
