设计目的
在电梯控制系统设计仿真练习中,学生将能够通过实际的项目实践,深入理解和掌握Verilog语言的应用、FPGA的设计方法以及仿真工具的使用。这种练习不仅培养了学生的工程设计能力,还能够帮助他们理解和分析实际硬件设计与仿真结果之间的关系,从而为未来的工程项目打下坚实的基础。
一、题目描述及功能
电梯控制器设计任务:设计一个四层楼房自动电梯控制器,用四个 LED显示电梯行进过程,并有数码管显示电梯当前所在楼层位置,在每层电梯入口处设有请求按钮开关,请求按钮按下则相应楼层的LED亮。用CLK脉冲控制电梯运动,每来一个CLK脉冲电梯升(降)一层。电梯到达有请求的楼层后,该层次的指示灯灭,电梯门打开(开门指示灯亮),开门5秒后,电梯门自动关闭,电梯继续运行。控制电路应能记忆所有楼层请求信号,并按如下运行规则依次相应:运行过程中先响应最早的请求,再响应后续的请求。如果无请求则停留当前层。如果有两个同时请求信号,则判断请求信号离当前层的距离,距离近请求的先响应,再响应较远的请求。每个请求信号保留至执行后清除。电梯出故障灯全灭并响起警报声,并自动在某一楼层停止运行,向总控通过spi协议发出sos信息。
(灯的功能没有实现,我是在仿真里直接硬改的。。 需要的自己写一下吧)
二、代码和仿真结果(下面代码是4层的,9层的在资源里面自行下载 可改成任意层数)
1.代码描述
//顶层模块,里面关于灯LED可以忽略 写的逻辑不对其他楼层正常 先看仿真结果 符不符合你的要求
// 电梯
// 输入:CLK:时钟,buttons:每个楼层的请求,如button[0]=1代表0楼有请求
// 输出:Layer 当前层数
`define TopLayer 5
`define BottomLayer 1
module elevator2(
input CLK, Reset,
input[`TopLayer - `BottomLayer :0] buttons,
input sos_key,
output reg[3:0] Layer,
output wire [6:0] a_g,
output reg [7:0] send_data,
output wire led_one,
output wire led_two,
output wire led_three,
output wire led_four,
output reg led_open,
output wire BEEP,
output wire O_tx_done ,
input I_spi_miso , // SPI串行输入,用来接收从机的数据
output wire O_spi_sck , // SPI时钟
output wire O_spi_cs , // SPI片选信号
output wire O_spi_mosi // SPI输出,用来给从机发送数据
);
// 状态定义
parameter UP = 001, DOWN = 010, IDLE = 100;
parameter LED_TIME = 3'd3;
// 状态寄存器
reg [2:0] CurrentState, NextState;
reg [2:0] tx_count;
reg [3:0] led_count;
// 加减计数器控制
reg positive, negative, freeze;
wire[3:0] Q;
reg [1:0] led1_temp;
reg [1:0] led2_temp;
reg [1:0] led3_temp;
reg [1:0] led4_temp;
// 加减计数器模块
Ad_Counter add(~CLK, Reset, positive, negative, freeze, Q);
assign BEEP=~sos_key;//蜂鸣器响
assign led_one=buttons[1]?1:Layer==1?1:0;//只要key1_r2的状态为1,那么小灯就亮
assign led_two=(buttons[2]&&(CLK==1'b0))?0:Layer==2?1:0;//只要key2按下,那么小灯就开始亮,直到到达对应楼层
assign led_three=(buttons[3]&&(CLK==1'b0))?0:Layer==3?1:0;
assign led_four=(buttons[4]&&(CLK==1'b0))?0:Layer==4?1:0;
always @(posedge CLK )
begin
if(Reset)
begin
led1_temp<=2'b00;
led2_temp<=2'b00;
led3_temp<=2'b00;
led4_temp<=2'b00;
end
else begin
led1_temp<={led1_temp[0],led_one};
led2_temp<={led2_temp[0],led_two};
led3_temp<={led3_temp[0],led_three};
led4_temp<={led4_temp[0],led_four};
end
end
always @(posedge CLK )
begin
if(Reset)
begin
led_open<=1'b0;
end
else if(led1_temp==2'b10 || led2_temp==2'b10 || led3_temp==2'b10 || led4_temp==2'b10)begin
led_open<=1'b1;
end
else if(led_count == LED_TIME)begin
led_open<=1'b0;
end
end
always @(posedge CLK )
begin
if(Reset)
begin
led_count<=3'd0;
end
else if (led_open==1'b1)begin
led_count<=led_count + 1'b1;
end
end
always @(posedge CLK )
begin
if(Reset)
begin
tx_count<=3'd0;
end
else if (O_tx_done==3'd1 && tx_count<3'd2)begin
tx_count<=tx_count + 1'b1;
end else if (O_tx_done==3'd1 && tx_count==3'd2)begin
tx_count<=3'd0;
end
else begin
tx_count<=tx_count;
end
end
always @(posedge CLK )
begin
if(Reset)
begin
send_data<=8'h00;
end
else if (tx_count==3'd0)begin
send_data<=8'h53;
end
else if (tx_count==3'd1)begin
send_data<=8'h4f;
end
else if (tx_count==3'd2)begin
send_data<=8'h53;
end else begin
send_data<=send_data;
end
end
// 根据当前状态调制加减计数器的工作方式
always @(CurrentState)
begin
positive <= CurrentState[0];
negative <= CurrentState[1];
freeze <= CurrentState[2];
end
// 将加减计数器的输出赋予Layer
always @(Q)
Layer <= Q;
// 时序逻辑
always @(posedge CLK)
begin
if(buttons[Layer] || buttons == 0)
CurrentState <= IDLE;
else
CurrentState <= NextState;
end
// 状态转移
always @(CurrentState, buttons)
begin
case (CurrentState)
// 电梯空闲时,优先方向:下降
IDLE :
if(buttons[0])
NextState <= DOWN;
else if(buttons[1])
NextState <= Layer < 1 ? UP : DOWN;
else if(buttons[2])
NextState <= Layer < 2 ? UP : DOWN;
else if(buttons[3])
NextState <= Layer < 3 ? UP : DOWN;
else if(buttons[4])
NextState <= UP;
// 电梯上升时,优先方向:上升
UP :
if(buttons[4])
NextState <= UP;
else if(buttons[3])
NextState <= Layer <= 3 ? UP : DOWN;
else if(buttons[2])
NextState <= Layer <= 2 ? UP : DOWN;
else if(buttons[1])
NextState <= Layer <= 1 ? UP : DOWN;
else if(buttons[0])
NextState <= DOWN;
// 电梯下降时,优先方向:下降
DOWN :
if(buttons[0])
NextState <= DOWN;
else if(buttons[1])
NextState <= Layer < 1 ? UP : DOWN;
else if(buttons[2])
NextState <= Layer < 2 ? UP : DOWN;
else if(buttons[3])
NextState <= Layer < 3 ? UP : DOWN;
else if(buttons[4])
NextState <= UP;
// 电梯启动时,优先方向:下降
default:
begin
if(buttons[0])
NextState <= DOWN;
else if(buttons[1])
NextState <= Layer < 1 ? UP : DOWN;
else if(buttons[2])
NextState <= Layer < 2 ? UP : DOWN;
else if(buttons[3])
NextState <= Layer < 3 ? UP : DOWN;
else if(buttons[4])
NextState <= UP;
end
endcase
end
ts_7dm ts_7dm_inst(
.num(Layer),
.a_g(a_g)
);
SPI_Master SPI_Master_inst
(
.I_clk(CLK) , // 全局时钟50MHz
.I_rst_n(!Reset) , // 复位信号,低电平有效
.I_tx_en(sos_key) , // 数据打包完成,可以进行spi传输标志
.I_data_in(send_data) , // 要发送的数据
.O_tx_done(O_tx_done),
.I_spi_miso(I_spi_miso) , // SPI串行输入,用来接收从机的数据
.O_spi_sck(O_spi_sck) , // SPI时钟
.O_spi_cs(O_spi_cs) , // SPI片选信号
.O_spi_mosi(O_spi_mosi) // SPI输出,用来给从机发送数据
);
endmodule
// 加减计数器
// 输入:positive:递增计数,negative:递减计数,freeze:冻结,使不工作
// 输出:Top:Q最大,Bottom:Q最小,
module Ad_Counter(
input CLK, Reset,
input positive, negative, freeze,
output reg[3:0] Q
);
parameter MAX = 9;
parameter MIN = 0;
always @(posedge CLK, posedge Reset)
begin
if(Reset)
Q <= 0;
else if(Q > MAX)
Q<= 0;
else if(freeze)
;
else if(positive)
Q <= Q==MAX ? Q :Q + 1'b1;
else if(negative)
Q <= Q==MIN ? Q :Q - 1'b1;
else
;
end
endmodule
//7段数码管译码器;
module ts_7dm(num,a_g);
input[3:0] num;
output[6:0] a_g;
reg[6:0] a_g;//a_g[6:0]-->{a,b,c,d,e,f,g}
always@(num) begin
case(num)
//4'd0:begin a_g<=7'b111_1110;end //带begin end上可以释放内存
4'd0:a_g<=7'b111_1110;
4'd1:a_g<=7'b011_0000;
4'd2:a_g<=7'b110_1101;
4'd3:a_g<=7'b111_1100;
4'd4:a_g<=7'b011_0011;
4'd5:a_g<=7'b101_1011;
4'd6:a_g<=7'b101_1111;
4'd7:a_g<=7'b111_0000;
4'd8:a_g<=7'b111_1111;
4'd9:a_g<=7'b111_1011;
default:a_g<=7'b000_0001;//中杠;
endcase
end
endmodule
//-------------------spi模块,输入是16位,其实还是分别发送2次8位-----------//
//只负责发送 不接受
module SPI_Master
(
input I_clk , // 全局时钟50MHz
input I_rst_n , // 复位信号,低电平有效
input I_tx_en , // 数据打包完成,可以进行spi传输标志
input [7:0] I_data_in , // 要发送的数据
output reg O_tx_done , //spi发送一个字节完毕标志位
// 四线标准SPI信号定义
input I_spi_miso , // SPI串行输入,用来接收从机的数据
output reg O_spi_sck , // SPI时钟
output reg O_spi_cs , // SPI片选信号
output reg O_spi_mosi // SPI输出,用来给从机发送数据
);
//reg [15:0] I_data_in=16'hF0_F0;
//-------------------spi工作状态 空闲或者发送-----------------------//
localparam IDLE = 5'b00001,
SEND = 5'b00010;
reg [4:0] work_state ; //spi工作状态
//-------------------SPI实际发送相关变量---------------------------//
reg [4:0] clk_count ; //spi_clk翻转计数器
reg [5:0] R_tx_state ; //spi实际发送数据状态
//-------------spi工作状态切换,根据I_tx_en和O_tx_done切换----------//
always @(posedge I_clk or negedge I_rst_n)
begin
if(!I_rst_n)
begin
work_state<=IDLE;
end
else begin
case(work_state)
IDLE:begin
if(I_tx_en == 1'b1) begin
work_state <= SEND;
end else begin
work_state <= IDLE;
end
end
SEND:begin
if(O_tx_done == 3'b010) begin
work_state <= IDLE;
end
else begin
work_state <= SEND;
end
end
endcase
end
end
//-------------spi发送状态,先发高8位,再发低8位--------------//
always @(posedge I_clk or negedge I_rst_n)
begin
if(!I_rst_n)
begin
R_tx_state <= 6'd0 ;
O_spi_cs <= 1'b1 ;
O_spi_sck <= 1'b0 ;
O_spi_mosi <= 1'b1 ;
O_tx_done <= 3'b0 ;
clk_count <= 5'd0 ;
end
else begin
case(work_state)
IDLE:begin
R_tx_state <= 6'd0 ;
O_spi_cs <= 1'b1 ;
O_spi_sck <= 1'b0 ;
O_spi_mosi <= 1'b1 ;
O_tx_done <= 3'b0 ;
clk_count <= 5'd0 ;
end
SEND:begin
case(R_tx_state)
6'd1, 6'd3 , 6'd5 , 6'd7 ,
6'd9, 6'd11, 6'd13:
begin
if(clk_count == 5'd4) begin
R_tx_state <= R_tx_state + 1'b1 ;
clk_count <= 5'd0 ;
end
else begin
clk_count <= clk_count + 1'b1 ;
O_spi_sck <= 1'b1 ;
O_tx_done <= O_tx_done ;
end
end
6'd0: // 发送第15位
begin
if(clk_count == 5'd4) begin
R_tx_state <= R_tx_state + 1'b1 ;
clk_count <= 5'd0 ;
end
else begin
clk_count <= clk_count + 1'b1;
O_spi_cs <= 1'b0 ; // 把片选CS拉低
O_spi_mosi <= I_data_in[7] ;
O_spi_sck <= 1'b0 ;
O_tx_done <= 3'b0 ;
end
end
6'd2: // 发送第14位
begin
if(clk_count == 5'd4) begin
R_tx_state <= R_tx_state + 1'b1 ;
clk_count <= 5'd0 ;
end
else begin
clk_count <= clk_count + 1'b1 ;
O_spi_mosi <= I_data_in[6] ;
O_spi_sck <= 1'b0 ;
O_tx_done <= O_tx_done ;
end
end
5'd4: // 发送第13位
begin
if(clk_count == 5'd4) begin
R_tx_state <= R_tx_state + 1'b1 ;
clk_count <= 5'd0 ;
end
else begin
clk_count <= clk_count + 1'b1 ;
O_spi_mosi <= I_data_in[5] ;
O_spi_sck <= 1'b0 ;
O_tx_done <= O_tx_done ;
end
end
6'd6: // 发送第12位
begin
if(clk_count == 5'd4) begin
R_tx_state <= R_tx_state + 1'b1 ;
clk_count <= 5'd0 ;
end
else begin
clk_count <= clk_count + 1'b1 ;
O_spi_mosi <= I_data_in[4] ;
O_spi_sck <= 1'b0 ;
O_tx_done <= O_tx_done ;
end
end
6'd8: // 发送第11位
begin
if(clk_count == 5'd4) begin
R_tx_state <= R_tx_state + 1'b1 ;
clk_count <= 5'd0 ;
end
else begin
clk_count <= clk_count + 1'b1 ;
O_spi_mosi <= I_data_in[3] ;
O_spi_sck <= 1'b0 ;
O_tx_done <= O_tx_done ;
end
end
6'd10: // 发送第10位
begin
if(clk_count == 5'd4) begin
R_tx_state <= R_tx_state + 1'b1 ;
clk_count <= 5'd0 ;
end
else begin
clk_count <= clk_count + 1'b1 ;
O_spi_mosi <= I_data_in[2] ;
O_spi_sck <= 1'b0 ;
O_tx_done <= O_tx_done ;
end
end
6'd12: // 发送第9位
begin
if(clk_count == 5'd4) begin
R_tx_state <= R_tx_state + 1'b1 ;
clk_count <= 5'd0 ;
end
else begin
clk_count <= clk_count + 1'b1 ;
O_spi_mosi <= I_data_in[1] ;
O_spi_sck <= 1'b0 ;
O_tx_done <= O_tx_done ;
end
end
6'd14: // 发送第8位
begin
if(clk_count == 5'd4) begin
R_tx_state <= R_tx_state + 1'b1 ;
clk_count <= 5'd0 ;
end
else begin
clk_count <= clk_count + 1'b1 ;
O_spi_mosi <= I_data_in[0] ;
O_spi_sck <= 1'b0 ;
end
end
6'd15: // 增加一个冗余状态
begin
if(clk_count == 5'd4) begin
R_tx_state <= R_tx_state + 1'b1 ;
clk_count <= 5'd0 ;
O_tx_done <= 1'b1 ;
end
else begin
clk_count <= clk_count + 1'b1 ;
O_spi_sck <= 1'b1 ;
end
end
6'd16: // 增加一个冗余状态
begin
if(clk_count == 5'd4) begin
R_tx_state <= R_tx_state + 1'b1 ;
clk_count <= 5'd0 ;
O_spi_cs <= 1'b1 ;
end
else begin
O_tx_done <= 1'b0 ;
clk_count <= clk_count + 1'b1 ;
O_spi_sck <= 1'b0 ;
end
end
default:R_tx_state <= 6'd0 ;
endcase
end
default:;
endcase
end
end
endmodule
tb文件:
// Copyright (C) 1991-2013 Altera Corporation
// Your use of Altera Corporation's design tools, logic functions
// and other software and tools, and its AMPP partner logic
// functions, and any output files from any of the foregoing
// (including device programming or simulation files), and any
// associated documentation or information are expressly subject
// to the terms and conditions of the Altera Program License
// Subscription Agreement, Altera MegaCore Function License
// Agreement, or other applicable license agreement, including,
// without limitation, that your use is for the sole purpose of
// programming logic devices manufactured by Altera and sold by
// Altera or its authorized distributors. Please refer to the
// applicable agreement for further details.
// *****************************************************************************
// This file contains a Verilog test bench template that is freely editable to
// suit user's needs .Comments are provided in each section to help the user
// fill out necessary details.
// *****************************************************************************
// Generated on "07/10/2024 18:33:06"
// Verilog Test Bench template for design : elevator2
//
// Simulation tool : ModelSim-Altera (Verilog)
//
`timescale 1 ns/ 1 ps
module elevator2_vlg_tst();
reg CLK;
reg Reset;
reg [4:0] buttons;
reg I_spi_miso;
reg sos_key;
// wires
wire [3:0] Layer;
wire [6:0] a_g;
wire O_spi_sck ;
wire O_spi_cs ;
wire O_spi_mosi ;
wire [7:0] send_data;
wire O_tx_done;
wire BEEP;
reg LED1;
reg LED2;
reg LED3;
reg LED4;
reg LED_OPEN;
// assign statements (if any)
elevator2 i1 (
// port map - connection between master ports and signals/registers
.CLK(CLK),
.Layer(Layer),
.sos_key(sos_key),
.Reset(Reset),
.a_g(a_g),
.BEEP(BEEP),
.buttons(buttons),
.I_spi_miso(I_spi_miso),
.O_spi_sck(O_spi_sck),
.O_spi_cs(O_spi_cs),
.O_spi_mosi(O_spi_mosi),
.send_data(send_data),
.O_tx_done(O_tx_done)
);
initial
begin
// 初始化
CLK = 0;
LED1=0;
LED2=0;
LED3=0;
LED4=0;
LED_OPEN=0;
I_spi_miso=1;
sos_key=0;
Reset = 1;
buttons = #2 5'b00001;
Reset = 0;
// 1楼请求电梯
LED_OPEN=1;
#4
LED_OPEN=0;
buttons = #4 10'b00010;
// 去4楼 和 3楼
LED4=1;
LED3=1;
buttons = #4 10'b11000;
#4
LED3=0;
LED_OPEN=1;
#4
LED_OPEN=0;
// 3楼下电梯 然后去四楼
buttons = #10 10'b10000;
#2
LED4=0;
LED_OPEN=1;
#4
LED_OPEN=0;
// 四楼上人 去3楼和 1楼
buttons = 10'b01010;
LED3=1;
LED1=1;
#4
LED3=0;
#4
LED_OPEN=1;
#4
LED_OPEN=0;
#8
// 到3楼 下车 然后去1楼
buttons = 10'b00010;
#4
LED1=0;
#4
LED_OPEN=1;
#4
LED_OPEN=0;
#8
// 1楼上人去4楼
buttons = 10'b10000;
LED4=1;
#2
// 中间 3楼上人 去4楼
LED3=1;
buttons = #2 10'b11000;
LED3=0;
LED_OPEN=1;
#4
LED_OPEN=0;
#4
// 去3楼和四楼
LED3=0;
buttons = #2 10'b10000;
#2
LED4=0;
LED_OPEN=1;
#4
LED_OPEN=0;
#4
#20 sos_key=1;
// // 取消5楼请求,4楼有请求
// buttons = #20 10'b0000001000;
// // 0、8楼有请求
// buttons = #20 10'b0100000001;
// // 此时应是方向优先原则,取消0楼请求,8楼有请求
// buttons = #20 10'b0100000000;
// buttons = #20 10'b0000001000;
// buttons = #20 10'b0100000000;
end
always
begin
CLK = #1 ~CLK;
end
endmodule
2.仿真结果
第一种情况:1楼请求电梯,到达1楼后,电梯门打开,楼层灯LED1亮起,LED_OPEN亮起,然后关门,灯灭。电梯内按钮按下3楼、4楼。这时候LED3和LED4亮起,电梯启动,电梯楼层由1-2-3然后停下。3楼到达,LED_OPEN亮起,门打开,门关闭,LED3灭。然后电梯继续前往4楼,到达四楼后,LED_OPEN亮起,门打开,LED4灭
第二种情况,第一种情况到达4楼后,上人,按下3楼和1楼的按钮,LED3、LED1亮起,电梯启动,到达3楼后,LED_OPEN亮起,然后关闭,LED3灭。然后继续向1楼前进,到达1楼后,LED_OPEN亮起,关门,LED1灭。
第三种情况,1楼上人,去四楼,LED4亮起,电梯运行之后,3楼有人请求电梯。LED3亮起,电梯停在了3楼,LED3灭,LED_OPEN亮起,关闭,电梯继续运行去4楼,到达4楼后,LED_OPEN亮起,关闭,LED4灭
第四种情况,电梯出现故障,这时候蜂鸣器BEEP变低电平,同时数据发送模块开始工作,产生SPI时钟,片选信号,按照“SOS”顺序进行发送。
第四种情况,电梯出现故障,这时候蜂鸣器BEEP变低电平,同时数据发送模块开始工作,产生SPI时钟,片选信号,按照“SOS”顺序进行发送。
总结
电梯就是你需要走出第一步,剩下的99步由我来走~(9层电梯工程以及本工程都上传到资源了,需要的自行下载,点赞收藏+评论谢谢)