十层电梯的Verilog设计

数电实习,老师给我们小组了一个不仅课本上找不到,网上也几乎搜不到相关资料的题目:设计一个电梯(笑)。这玩意儿看着难,其实一点儿也不简单,还好最后做出来了。

首先我们要明白现在的电梯是怎么运行的。如果是先到先服务,那所有坐电梯的人都要骂设计师:有可能你在某一层看着电梯经过你的楼层N次而不停,原因是有人比你先按按钮。另一种方式是就近原则,即谁离电梯近电梯先服务谁,这样低楼层的享福,高楼层的要骂设计师。因为低楼层往往人流量大,电梯就在低楼层一直接客,高楼层的人一直等不到电梯。于是大家想出来了第三种方案:让电梯在最高和最低楼层间往返,这样所有人都能在有限时间内坐到电梯。第三种方案稍加改进就是当今广泛使用的电梯运行逻辑:在有请求的最高—最低楼层间往返,上行的时候不管电梯下边的楼层,下行的时候不管电梯上边的楼层。这样能最大确保效率与公平。

明白了电梯怎么运行,我们就要考虑输入和输出。一般而言,电梯有两个输入:一个是乘客在某一楼层按召唤电梯的按钮(call),另一个是乘客在电梯里按选择楼层的按钮(select);一个输出:电梯当前所在楼层( current_floor)。剩下的就是时序逻辑电路的状态,我们定义了如下几个状态:

  1. request,储存了电梯要去的楼层
  2. state,1表示电梯静止,0表示电梯运行
  3. direction,1表示上行,0表示下行
  4. current_floor_reg,四位二进制数,储存电梯当前楼层,0000是1楼,1001是10楼。

后来为了检查波形,我们又把这些中间状态也作为输出给显示出来了,在此就不再赘述。

我们的电梯呈现一个三级电路结构。

第一级:输入call、select、上一个request,输出新的request和state。输入都是十位二进制数(10'b),每位为1表示有效,0表示无效,从右到左楼层依次升高。例如0011010010表示2,5,7,8楼有人按坐电梯的按钮。新的request的每一位都是三个输入的相应位做或运算,即三个输入的某位只要有一个是1,新request的对应位就是1。然后如果request不是全0,state就设为0表示电梯要继续运行,否则state设1表示电梯静止。第一级的作用就是获得要去的楼层,并设置电梯状态。

第二级:输入request、current_floor_reg、上一个direction,输出新的direction。这是最麻烦的部分,需要判断电梯原来的运行状态、所在楼层和request的关系。判断方式为:如果电梯本来在上行,且比电梯当前所在楼层高的楼层有请求,那么继续向上运行,否则向下运行;如果电梯本来在下行,且比电梯当前所在楼层低的楼层有请求,那么继续向下运行,否则向上运行;例如current_floor_reg=0100,direction=0,即电梯现在在5楼且正在下行,request=××××××0100,可以看到3楼有请求,那么电梯会继续下行,倘若此时request=××××××0000,即1-4楼都没有请求,那么电梯会转为上行。这个掉头至关重要,否则电梯就只能向一个方向运行了。

第三级:输入direction、current_floor_reg,输出新的current_floor_reg。如果direction=1上行,current_floor_reg+1;如果direction=0下行,current_floor_reg-1。第三级的本质是一个可逆二进制计数器。

以上就是总体思路,另外有一些零碎的要点单独说一下:

  1. 怎么表示电梯的运行速度?CLK本身就能表示。我们设定是每来一个CLK上升沿电梯就上一层或下一层,那么电梯上下一层的时间就是一个CLK周期。
  2. call不区分上下行吗?可以区分,这样更符合现实,但是会让代码变得更加复杂,我们没有足够时间(工程光学摄影测量数电期末说的就是你们仨)。其实不区分上下行电梯也能运行的很好,这是因为人进了电梯肯定要再按select,这个select本身已经包含了上或下的信息。
  3. 每到一个目标楼层要把request的对应位置0,否则电梯就停不下来了。
  4. 怎么表示电梯到地方停靠?直接把state置1就行了,不用担心电梯会就此一直停着,到了下一个CLK如果request不是全0它会继续运行的。
  5. 模拟的时候记得把输入信号调持续时间长一点,一定要让它活到CLK上升沿的到来,否则就没法接收到了。

暂时想到这么多,其他的想到再在评论区说吧。码了这么多字,其实把代码粘上来大家一看就懂了。

module elevator(
	input wire clk,                 // 时钟信号
	input wire reset,               // 复位信号
	input wire [9:0] call,          // 外部请求,即乘客在某层按电梯按钮
	input wire [9:0] select,        // 内部请求,即乘客在电梯内选择楼层
	output reg [3:0] current_floor,  // 当前楼层
	output reg state_reg,                     // 用于输出电梯状态中间变量,检查代码
	output reg direction_reg,                 // 用于输出电梯方向中间变量,检查代码
	output reg [9:0] request_reg              // 用于输出请求中间变量,检查代码
);
	reg [3:0] current_floor_reg;   	  // 电梯当前所在楼层
	reg state;			  // 电梯状态,1为静止,0为运行
	reg direction;               	  // 电梯方向,1为上升,0为下降
	reg [9:0] request;	          // 储存了电梯要去的楼层
	always @(posedge clk)  // 上升沿触发
	begin
		if (reset) 
		begin 
			current_floor_reg = 4'b0000; // 电梯去一楼罚站
			state = 1; // 状态设为停靠
			direction = 1; // 方向只能上升
			request = 10'b0000000000; // 请求清零
		end 
		else 
		// 第一级电路
		begin
			request = call | select | request; // 对call,select,上一个request的每一位做或运算,只要有一个是1就要去该楼层
                        if (request) 
				state = 0; // 如果有请求,状态为运行
			else
				state=1; // 如果没有请求,状态为停止
			//二级电路
			if(!state)
			begin
				if (direction == 1) //当电梯方向为上行
				begin // 判断当前楼层上方是否有更高楼层的请求,如果有,方向仍然为上行,如果没有,方向改为下行
					case (current_floor_reg)
					0: direction = 1; // 1楼只能上行
					1:	if (request[2] != 0 || request[3] != 0 || request[4] != 0 || request[5] != 0 || request[6] != 0 || request[7] != 0 || request[8] != 0 || request[9] != 0) 
						begin
							direction = 1; 
						end
						else
						begin
							direction = 0;
						end
					2: 	if (request[3] != 0 || request[4] != 0 || request[5] != 0 || request[6] != 0 || request[7] != 0 || request[8] != 0 || request[9] != 0) 
						begin
							direction = 1; 
						end
						else
						begin
							direction = 0;
						end
					3: 	if (request[4] != 0 || request[5] != 0 || request[6] != 0 || request[7] != 0 || request[8] != 0 || request[9] != 0) 
						begin
							direction = 1; 
						end
						else
						begin
							direction = 0;
						end
					4: 	if (request[5] != 0 || request[6] != 0 || request[7] != 0 || request[8] != 0 || request[9] != 0)
						begin
							direction = 1; 
						end
						else
						begin
							direction = 0;
						end
					5: 	if (request[6] != 0 || request[7] != 0 || request[8] != 0 || request[9] != 0) 
						begin
							direction = 1;
						end
						else
						begin
							direction = 0;
						end
					6:	 if (request[7] != 0 || request[8] != 0 || request[9] != 0)
						begin
							direction = 1;
						end
						else
						begin
							direction = 0;
						end
					7:	 if (request[8] != 0 || request[9] != 0) 
						begin
							direction = 1; 
						end
						else
						begin
							direction = 0;
						end
					8:	 if (request[9] != 0) 
						begin
						direction = 1; 
						end
						else
						begin
							direction = 0;
						end
					default: direction = 0; // 10楼只能下行
					endcase
				end
				else if (direction == 0) // 如果当前方向为下行
				begin // 判断当前楼层下方是否有更低楼层的请求,如果有,方向仍然为下行,如果没有,方向改为上行
					case (current_floor_reg)
					1:	if (request[0] != 0)
						begin
							direction = 0; 
						end
						else
						begin
							direction = 1;
						end
					2: 	if (request[0] != 0 || request[1] != 0) 
						begin
							direction = 0; 
						end
						else
						begin
							direction = 1;
						end
					3:	if (request[0] != 0 || request[1] != 0 || request[2] != 0)
						begin
							direction = 0; 
						end
						else
						begin
							direction = 1;
						end
					4:	if (request[0] != 0 || request[1] != 0 || request[2] != 0 || request[3] != 0)
						begin
							direction = 0; 
						end
						else
						begin
							direction = 1;
						end
					5:	if (request[0] != 0 || request[1] != 0 || request[2] != 0 || request[3] != 0 || request[4] != 0)
						begin
							direction = 0; 
						end
						else
						begin
							direction = 1;
						end
					6:	if (request[0] != 0 || request[1] != 0 || request[2] != 0 || request[3] != 0 || request[4] != 0 || request[5] != 0)
						begin
							direction = 0; 
						end
						else
						begin
							direction = 1;
						end
					7:	if (request[0] != 0 || request[1] != 0 || request[2] != 0 || request[3] != 0 || request[4] != 0 || request[5] != 0 || request[6] != 0)
						begin
							direction = 0; 
						end
						else
						begin
							direction = 1;
						end
					8:	if (request[0] != 0 || request[1] != 0 || request[2] != 0 || request[3] != 0 || request[4] != 0 || request[5] != 0 || request[6] != 0 || request[7] != 0) 
						begin
							direction = 0; 
						end
						else
						begin
							direction = 1;
						end
					9:	direction=0; // 10楼只能下行
					default: direction = 1; // 1楼只能上行
					endcase
				end
				// 第三级电路,本质上是可逆二进制计数器
				if (direction == 1)
				begin
					current_floor_reg = current_floor_reg + 1; // 方向为上行,楼层+1
				end
				else if (direction == 0)
				begin
					current_floor_reg = current_floor_reg - 1; // 方向为下行,楼层-1
				end
				if (request[current_floor_reg])
				begin
					request[current_floor_reg] = 0;  // 如果到达请求楼层,请求置0
					state = 1; // 到达请求楼层,电梯停止
				end
			end
			else
				begin
				current_floor_reg=current_floor_reg; // 输出当前楼层
				end
		end
		// 输出中间变量,便于检查代码
		current_floor = current_floor_reg; 
		state_reg=state;
		direction_reg=direction;
		request_reg=request;
	end
endmodule

我们在判断方向时用了穷举法。其实本来不想这么做的,比如用if(request[2:0])表示1-3楼有请求,但是一直报错,就只能穷举了。

用ModelSim仿真的控制代码如下:

module elevator_tb;
    reg clk;//定义时钟
    reg reset;//定义复位标志
    reg [9:0] call;//定义外部输入按钮
    reg [9:0] select;//定义内部输入按钮
    wire [3:0] current_floor;//定义当前楼层
    wire state_reg;//定义中间变量状态,用于检查控制代码
    wire direction_reg;//定义中间变量方向,用于检查控制代码               
    wire [9:0] request_reg;//定义请求变量             
    elevator uut (//实例化电梯模型
        .clk(clk),
        .reset(reset),
        .call(call),
        .select(select),
        .current_floor(current_floor),
        .state_reg(state_reg),
        .direction_reg(direction_reg),
        .request_reg(request_reg)
    );
 
    always begin
        #5 clk = ~clk;//设置时钟为5ns
    end
    initial begin
clk = 0; call = 10'b0; select =10'b0;//初始设置在1楼 
#0;
reset = 1;//先复位电梯
#10;
reset = 0;//10s后复位结束
#30
call = 10'b0000000010;//30s后2楼外部请求
#10
call = 10'b0;//10s后请求结束
#50
select = 10'b0010000000;//50s后8楼内部请求
#10
select=10'b0;//10s后请求结束
#50
call=10'b0000010000;//50s后5楼外部请求
#10
call=10'b0;//10s后请求结束
#50
select=10'b0100000100;//50s后9楼和3楼同时内部请求
#10
select=10'b0;//10s后请求结束
#20
call=10'b0000001000;//20s后4楼外部请求
#10
call=10'b0;//10s后请求结束
#500 $stop;//500s后停止仿真
    end
    initial begin
        $monitor("At time %t, current floor = %d", $time, current_floor);//显示输出
    end
endmodule

这是跑出来的波形:

根据上面的结果可以看到:

①在0-10s的时候,电路在第一个上升沿5s的时候进行了复位,将电梯设置在1楼;

②在40-50s的时候,有2楼的外部请求,电梯在请求后的第一个上升沿45s的时候进行了相应,开始上升,到达2楼;

③在100-110s的时候,有8楼的内部请求,电梯在请求后的第一个上升沿开始相应,并且上升到8楼;

④在160-170s的时候,有5楼的外部请求,电梯请求后的第一个上升沿开始相应,并且下降到5楼;

⑤在第220-230s的时候,有9楼和3楼同时有内部请求,此时电梯停止在5楼,但是方向处于下降状态,所以电梯在请求后的第一个上升沿开始相应,并且先下降到3楼,再上升到9楼;

⑥在第250-260s的时候,有4楼的外部请求,电梯在请求后的第一个上升沿开始相应,并且下降到4楼,然后停止在4楼;

逆天CSDN,添加文章标签的时候找不到Verilog和ModelSim

最后感谢我的组员们!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值