数电实习,老师给我们小组了一个不仅课本上找不到,网上也几乎搜不到相关资料的题目:设计一个电梯(笑)。这玩意儿看着难,其实一点儿也不简单,还好最后做出来了。
首先我们要明白现在的电梯是怎么运行的。如果是先到先服务,那所有坐电梯的人都要骂设计师:有可能你在某一层看着电梯经过你的楼层N次而不停,原因是有人比你先按按钮。另一种方式是就近原则,即谁离电梯近电梯先服务谁,这样低楼层的享福,高楼层的要骂设计师。因为低楼层往往人流量大,电梯就在低楼层一直接客,高楼层的人一直等不到电梯。于是大家想出来了第三种方案:让电梯在最高和最低楼层间往返,这样所有人都能在有限时间内坐到电梯。第三种方案稍加改进就是当今广泛使用的电梯运行逻辑:在有请求的最高—最低楼层间往返,上行的时候不管电梯下边的楼层,下行的时候不管电梯上边的楼层。这样能最大确保效率与公平。
明白了电梯怎么运行,我们就要考虑输入和输出。一般而言,电梯有两个输入:一个是乘客在某一楼层按召唤电梯的按钮(call),另一个是乘客在电梯里按选择楼层的按钮(select);一个输出:电梯当前所在楼层( current_floor)。剩下的就是时序逻辑电路的状态,我们定义了如下几个状态:
- request,储存了电梯要去的楼层
- state,1表示电梯静止,0表示电梯运行
- direction,1表示上行,0表示下行
- 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。第三级的本质是一个可逆二进制计数器。
以上就是总体思路,另外有一些零碎的要点单独说一下:
- 怎么表示电梯的运行速度?CLK本身就能表示。我们设定是每来一个CLK上升沿电梯就上一层或下一层,那么电梯上下一层的时间就是一个CLK周期。
- call不区分上下行吗?可以区分,这样更符合现实,但是会让代码变得更加复杂,我们没有足够时间(工程光学摄影测量数电期末说的就是你们仨)。其实不区分上下行电梯也能运行的很好,这是因为人进了电梯肯定要再按select,这个select本身已经包含了上或下的信息。
- 每到一个目标楼层要把request的对应位置0,否则电梯就停不下来了。
- 怎么表示电梯到地方停靠?直接把state置1就行了,不用担心电梯会就此一直停着,到了下一个CLK如果request不是全0它会继续运行的。
- 模拟的时候记得把输入信号调持续时间长一点,一定要让它活到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
最后感谢我的组员们!