2025寒假练 - 基于小脚丫FPGA实现Breakout游戏
该项目使用了STEP Baseboard4.0底板+STEP MXO2 LPC核心板,实现了BreakOut游戏的设计,它的主要功能为:1. 使用FPGA逻辑来设计BreakOut 游戏,能够利用TFTLCD以及HDMI驱动显示器使用(可切换),并通过按键和加速度计控制挡板左右移动; 2. 使用数码管记录分数以及胜利、失败的局数:小球每打掉一块砖块则记一分,当清空全部砖块,分数重置为0,胜利局数加1; 当使用挡板没有接到小球则复位游戏进程并初始化小球的位置,分数重置为0,失败局数加1; 3. 每次胜利蜂鸣器播放一段5s的音乐、每次失败小脚丫核心板上的8颗LED灯以呼吸灯的方式闪烁5s。。
标签
FPGA
Anon_Tokoy
更新2025-03-17
华中科技大学
111

项目介绍

1. 使用FPGA逻辑来设计BreakOut 游戏,能够利用TFTLCD以及HDMI驱动显示器使用(可切换),并通过按键和姿态传感器控制挡板左右移动;
2. 使用数码管记录分数以及胜利、失败的局数:小球每打掉一块砖块则记一分,当清空全部砖块,分数重置为0,胜利局数加1;
当使用挡板没有接到小球则复位游戏进程并初始化小球的位置,分数重置为0,失败局数加1;
3. 每次胜利蜂鸣器播放一段5s的音乐、每次失败小脚丫核心板上的8颗LED灯以呼吸灯的方式闪烁5s。

使用板卡:STEP Baseboard4.0底板+STEP MXO2 LPC核心板

4.按键还可以控制挡板和小球移动速度

软硬件平台

1.Lattice DIamond:

莱迪思Diamond是一款功能强大的FPGA设计软件,专为莱迪思FPGA系列量身定制,提供高效的设计流程和丰富的功能。它支持多种设计输入方式,包括Verilog、VHDL、EDIF和原理图等,允许在单个项目中混合使用不同设计语言。Diamond软件还提供项目管理、综合、布局布线、仿真和调试等一站式设计工具,具有直观的用户界面和强大的分析功能,如时序分析、资源使用报告和功耗计算等,帮助用户快速实现设计目标。

2.LCMXO2-4000HC-5MG132C:

LCMXO2-4000HC-5MG132C是莱迪思半导体公司推出的一款高性能、低功耗FPGA芯片,属于MachXO2系列。该芯片采用先进的工艺技术,具有丰富的逻辑资源和I/O功能,适用于各种嵌入式系统设计和工业控制应用。其主要特性包括高密度逻辑单元、多用途I/O引脚、内置存储器块、高速差分I/O支持以及多种电源管理选项,能够满足不同应用场景的需求。

3.STEP BaseBoard V4.0:

STEP BaseBoard V4.0是一款功能完善的开发平台,集成了多种外设接口和丰富的资源,为FPGA开发提供了便利的硬件环境。它支持多种FPGA芯片,包括莱迪思、Xilinx等主流厂商的产品,方便用户进行不同项目的开发和测试。该开发板具备标准的接口配置,如USB、串口、以太网等,还配备了丰富的外设扩展接口,如GPIO、SPI、I2C等,满足各种外设连接需求。此外,它还提供完善的开发工具链和丰富的示例代码,帮助用户快速上手和进行项目开发。


功能框图

设计思路

LCD初始化等代码使用官方样例lab13_picture_display,此处不再赘述。

仅介绍核心模块GameControl的内容:

1.设置状态机

localparam  STATE0_IDLE 								= 5'h0,   	// 状态0
STATE1_ShowBeginPic = 5'h1, // 状态1
STATE2_Init_Propreties = 5'h2, // 状态2
STATE3_Init_Info = 5'h3, // 状态3
STATE4_Init_Graphic = 5'h4, // 状态4
STATE5_Scan_Key = 5'h5, // 状态5
STATE6_Update_Propreties = 5'h6, // 状态6
STATE7_Update_Info = 5'h7, // 状态7
STATE8_Update_Graphic = 5'h8, // 状态8
STATE9_Decide_GameSatus = 5'h9, // 状态9
STATE10_GameOver_Win = 5'h10, // 状态10
STATE11_GameOver_Lose = 5'h11, // 状态11
STATE12_Decide_Restart = 5'h12, // 状态12
DONE = 5'h13; // 完成状态

状态机的含义大抵跟名称一致。

状态机更迭部分略。

2.设置游戏属性参数

如:

//*************************************// 砖块
reg [13 : 0] brick_exist_map [7 : 0]; // 砖块存在情况
localparam brick_map_start_x = 9'd170,
brick_map_start_y = 9'd20,
brick_width = 9'd20,
brick_height = 9'd6;

localparam Brick_Map_left_bottom_x = 9'd170,
Brick_Map_left_bottom_y = 9'd20;
//*************************************// 小球
reg [16 : 0] ball_cur_x, // 小球位置
ball_cur_y;
reg [8 : 0] ball_pre0_x,
ball_pre0_y;

reg ball_move_dir_x,
ball_move_dir_y;
reg [15 : 0] ball_velocity_x,
ball_velocity_y;

localparam ball_start_x = 9'd25,
ball_start_y = 9'd158,
ball_width = 9'd4,
ball_height = 9'd4;

reg flag_ball_speed_modify;

reg flag_tail_open; // 启用拖尾
.........(参数过多,此处仅展示部分属性)​

3.明确LCD显示屏启动更新标志位

// 子模块启动标志位
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
show_string_flag <= 1'b0; // 复位时清零
else if(state == STATE0_IDLE && show_game_flag)
show_string_flag <= 1'b1;
else if(state == STATE4_Init_Graphic && state4_cnt_show_item < MAX_S4_ITEM_NUM && syn_show_string_idle && ~state4_finish_flag)
show_string_flag <= 1'b1;
else if(state == STATE8_Update_Graphic && state8_cnt_show_item < MAX_S8_ITEM_NUM && syn_show_string_idle && ~state8_finish_flag)
show_string_flag <= 1'b1;
else if(state == STATE12_Decide_Restart && state12_finish_flag)
show_string_flag <= 1'b1;
else
show_string_flag <= 1'b0;
end

4.根据矩阵按键输入更新挡板移动速度和小球移动速度

// 挡板移动速度控制
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
paddle_move_speed <= 4'd1;
else if (state == STATE0_IDLE)
paddle_move_speed <= 4'd1;
else if(syn_pos_keyboard_status[2])
if (paddle_move_speed <= 1'd1)
paddle_move_speed <= 4'd1;
else
paddle_move_speed <= paddle_move_speed - 4'd1;
else if (syn_pos_keyboard_status[3])
if (paddle_move_speed >= 4'd5)
paddle_move_speed <= 4'd5;
else
paddle_move_speed <= paddle_move_speed + 4'd1;
else
paddle_move_speed <= paddle_move_speed;
end

// 小球移动速度控制
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
ball_velocity_x <= 16'd164;
ball_velocity_y <= 16'd256;
end else if (state == STATE0_IDLE) begin
ball_velocity_x <= 16'd164;
ball_velocity_y <= 16'd256;
end else if(state == STATE5_Scan_Key)
if (keyboard_status[6] & ~flag_ball_speed_modify)
if (ball_velocity_x <= 16'd32)
ball_velocity_x <= 16'd32;
else
ball_velocity_x <= ball_velocity_x - 16'd1;
else if (keyboard_status[7] & ~flag_ball_speed_modify)
if (ball_velocity_x >= 16'd2560)
ball_velocity_x <= 16'd2560;
else
ball_velocity_x <= ball_velocity_x + 16'd1;
else if (keyboard_status[10] & ~flag_ball_speed_modify)
if (ball_velocity_y <= 16'd32)
ball_velocity_y <= 16'd32;
else
ball_velocity_y <= ball_velocity_y - 16'd1;
else if (keyboard_status[11] & ~flag_ball_speed_modify)
if (ball_velocity_y >= 16'd2048)
ball_velocity_y <= 16'd2048;
else
ball_velocity_y <= ball_velocity_y + 16'd1;
else
paddle_move_speed <= paddle_move_speed;
end

5.更新挡板移动

// 游戏状态信息更新: 挡板移动
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
paddle_cur_x <= paddle_start_x;
paddle_cur_y <= paddle_start_y;
end else if(state == STATE2_Init_Propreties)
paddle_cur_y <= paddle_start_y;
else if(state == STATE5_Scan_Key)
if (keyboard_status[0] & ~flag_frame_paddle_move)
if (paddle_cur_y <= 9'd21)
paddle_cur_y <= 9'd20;
else
paddle_cur_y <= paddle_cur_y - paddle_move_speed;
else if (keyboard_status[1] & ~flag_frame_paddle_move)
if (paddle_cur_y >= 9'd299 - paddle_width)
paddle_cur_y <= 9'd300 - paddle_width;
else
paddle_cur_y <= paddle_cur_y + paddle_move_speed;
else
paddle_cur_y <= paddle_cur_y;
else if (acce_x[15] == 1'b0 && (acce_x[7] == 1'b1 || acce_x[6] == 1'b1) && ~flag_frame_paddle_move)
if (paddle_cur_y >= 9'd299 - paddle_width)
paddle_cur_y <= 9'd300 - paddle_width;
else
paddle_cur_y <= paddle_cur_y + {7'b0, acce_x[7], acce_x[6]};
else if (acce_x[15] == 1'b1 && (acce_x[7] == 1'b0 || acce_x[6] == 1'b0) && ~flag_frame_paddle_move)
if (paddle_cur_y <= 9'd21)
paddle_cur_y <= 9'd20;
else
paddle_cur_y <= paddle_cur_y - {7'b0, ~acce_x[7], ~acce_x[6]};
else begin
paddle_cur_y <= paddle_cur_y;
end
end

6.小球移动碰撞检测,并且计算应消除砖块和计算得分

// 游戏状态信息更新: 小球移动与得分计算
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
for (i = 0;i < 8;i = i + 1) begin: for_loop_game_propreties1
brick_exist_map[i] <= ~14'd0;
end
ball_cur_x[16 : 8] <= ball_start_x;
ball_cur_y[16 : 8] <= ball_start_y;
ball_move_dir_x <= 1'b0;
ball_move_dir_x <= 1'b0;
score <= 8'd0;
lives <= initial_lives;
erase_brick_x <= 9'd0;
erase_brick_y <= 9'd0;
// 初始化
end else if(state == STATE2_Init_Propreties) begin
for (i = 0;i < 8;i = i + 1) begin: for_loop_game_propreties2
brick_exist_map[i] <= ~14'd0;
end
ball_cur_x[16 : 8] <= ball_start_x;
ball_cur_y[16 : 8] <= ball_start_y;
ball_move_dir_x <= 1'b0;
ball_move_dir_x <= 1'b0;
score <= 8'd0;
lives <= initial_lives;
erase_brick_x <= 9'd0;
erase_brick_y <= 9'd0;
end else if (syn_pos_clk_fps) begin
// 边界处理
if (ball_cur_y[16 : 8] < 9'd20) begin
if (ball_move_dir_x == 1'b0) // 正向
ball_cur_x <= ball_cur_x + ball_velocity_x;
else // 负向
ball_cur_x <= ball_cur_x - ball_velocity_x;

ball_move_dir_y = 1'b0;
ball_cur_y[16 : 8] = 9'd20;
end else if (ball_cur_y[16 : 8] > 9'd300 - ball_width) begin
if (ball_move_dir_x == 0) // 正向
ball_cur_x <= ball_cur_x + ball_velocity_x;
else // 负向
ball_cur_x <= ball_cur_x - ball_velocity_x;

ball_move_dir_y = 1'b1;
ball_cur_y[16 : 8] = 9'd300 - ball_width;
end else if (ball_cur_x[16 : 8] < 9'd15) begin
......
end else if (ball_cur_x[16 : 8] > 9'd218 - ball_height) begin
......
///*
// 检测小球与砖块碰撞
end else if (ball_cur_x[16 : 8] >= Brick_Map_left_bottom_x - ball_height) begin
if (ball_move_dir_x == 1'b0) begin // 正向
if (brick_exist_map[if_collapse_Map_row][if_collapse_Map_column] == 1) begin
brick_exist_map[if_collapse_Map_row][if_collapse_Map_column] = 0;
erase_brick_x <= Brick_Map_left_bottom_x + brick_height * if_collapse_Map_row;
erase_brick_y <= Brick_Map_left_bottom_y + brick_width * if_collapse_Map_column;
ball_move_dir_x = 1'b1;
ball_cur_x <= ball_cur_x - ball_velocity_x;
score <= score + 8'd1;
end else begin
ball_cur_x <= ball_cur_x + ball_velocity_x;
end
end else if (ball_move_dir_x == 1'b1) begin // 负向
if (if_collapse_Map_row > 0
&& (ball_cur_x < Brick_Map_left_bottom_x + brick_height * if_collapse_Map_row + ball_height)
&& brick_exist_map[if_collapse_Map_row - 4'b1][if_collapse_Map_column] == 1'b1) begin
brick_exist_map[if_collapse_Map_row - 4'd1][if_collapse_Map_column] = 1'b0;
erase_brick_x <= Brick_Map_left_bottom_x + brick_height * (if_collapse_Map_row - 4'b1);
erase_brick_y <= Brick_Map_left_bottom_y + brick_width * if_collapse_Map_column;
ball_move_dir_x = 1'b0;
ball_cur_x <= ball_cur_x + ball_velocity_x;
score <= score + 8'd1;
end else begin
ball_cur_x <= ball_cur_x - ball_velocity_x;
end
end

if (ball_move_dir_y == 1'b0) begin // 正向
......
end
// 检测小球与挡板碰撞
end else if (ball_cur_x[16 : 8] < paddle_cur_x + paddle_height && ball_move_dir_x == 1'b1) begin
if (ball_cur_x[16 : 8] > paddle_start_x
&& ball_cur_y[16 : 8] >= paddle_cur_y
&& ball_cur_y[16 : 8] < paddle_cur_y + paddle_width - ball_width) begin
ball_move_dir_x = 1'b0;
ball_cur_x <= ball_cur_x + ball_velocity_x;
end else
ball_cur_x <= ball_cur_x - ball_velocity_x;

if (ball_move_dir_y == 1'b0) // 负向
ball_cur_y <= ball_cur_y + ball_velocity_y;
else // 负向
ball_cur_y <= ball_cur_y - ball_velocity_y;
// 默认情况下位移加上速度
end else begin
if (ball_move_dir_x == 1'b0) // 正向
ball_cur_x <= ball_cur_x + ball_velocity_x;
else // 负向
ball_cur_x <= ball_cur_x - ball_velocity_x;

if (ball_move_dir_y == 1'b0) // 正向
ball_cur_y <= ball_cur_y + ball_velocity_y;
else // 负向
ball_cur_y <= ball_cur_y - ball_velocity_y;
end
end else begin
ball_cur_x <= ball_cur_x;
ball_cur_y <= ball_cur_y;
end
end

7.LCD驱动显示更新

// 显示信息更新
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
......
end else if(state == STATE4_Init_Graphic) begin // 显示砖块
cur_show_type <= Type_brick;
cur_width <= brick_width;
cur_height <= brick_height;

cur_set_x <= Brick_Map_left_bottom_x + 9'd6 * state4_cnt_show_item;
cur_set_y <= Brick_Map_left_bottom_y;
cur_string_length <= 5'd14;
case (state4_cnt_show_item)
4'd0: cur_font_color <= BLUE;
4'd1: cur_font_color <= YELLOW;
4'd2: cur_font_color <= CYAN;
4'd3: cur_font_color <= BRED;
4'd4: cur_font_color <= WHITE;
4'd5: cur_font_color <= GRAY;
4'd6: cur_font_color <= BRED;
4'd7: cur_font_color <= BLACK;
endcase
cur_backgroud_color <= COLOR_BACKGROUND;
end else if(state == STATE8_Update_Graphic && state8_cnt_show_item == 4'd0) begin
cur_show_type <= Type_erase; // 擦除砖块
cur_width <= brick_width;
cur_height <= brick_height;

cur_set_x <= erase_brick_x;
cur_set_y <= erase_brick_y;
cur_string_length <= 5'd1;
cur_font_color <= COLOR_BACKGROUND;
cur_backgroud_color <= COLOR_BACKGROUND;
end else if(state == STATE8_Update_Graphic && state8_cnt_show_item == 4'd1) begin
cur_show_type <= Type_erase; // 擦除挡板
cur_width <= paddle_width + 9'd2;
cur_height <= paddle_height + 9'd2;

cur_set_x <= paddle_pre_x - 9'd1;
cur_set_y <= paddle_pre_y - 9'd1;
cur_string_length <= 5'd1;
cur_font_color <= COLOR_BACKGROUND;
cur_backgroud_color <= COLOR_BACKGROUND;
end else if(state == STATE8_Update_Graphic && state8_cnt_show_item == 4'd2) begin
cur_show_type <= Type_paddle; // 显示挡板
......
end else if(state == STATE8_Update_Graphic && state8_cnt_show_item == 4'd3) begin
cur_show_type <= Type_erase; // 擦除小球--先前位置
......
end else if(state == STATE8_Update_Graphic && state8_cnt_show_item == 4'd4) begin
cur_show_type <= Type_ball; // 显示小球
cur_width <= ball_width;
cur_height <= ball_height;

cur_set_x <= ball_cur_x[16 : 8];
cur_set_y <= ball_cur_y[16 : 8];
cur_font_color <= COLOR_BALL;
cur_backgroud_color <= COLOR_BACKGROUND;

if (ball_pre0_x != ball_cur_x[16 : 8] && ball_pre0_y != ball_cur_y[16 : 8]) begin
ball_pre0_x <= ball_cur_x[16 : 8];
ball_pre0_y <= ball_cur_y[16 : 8];
end
end
end

8.HDMI驱动颜色数据显示更新

wire [7 : 0]	HDMI_pos_1 = ball_width * HDMI_Lib_r + ball_width - 8'd1 - HDMI_Lib_c;
wire [7 : 0] HDMI_pos_2 = paddle_width * HDMI_Lib_r + paddle_width - 8'd1 - HDMI_Lib_c;
wire [7 : 0] HDMI_pos_3 = brick_width * HDMI_Lib_r + brick_width - 8'd1 - HDMI_Lib_c;


wire [15 : 0] HDMI_Type1_Lib = {
4'b0110,
4'b1111,
4'b1111,
4'b0110
};
wire [103 : 0] HDMI_Type2_Lib = {
26'b11111111111111111111111111,
26'b10000100001000010000100001,
26'b10000100001000010000100001,
26'b11111111111111111111111111
};
wire [119 : 0] HDMI_Type3_Lib = {
20'b00000000000000000000,
20'b01111111111111111110,
20'b01111111111111111110,
20'b01111111111111111110,
20'b01111111111111111110,
20'b01111111111111111110
};
//*************************************// HDMI
// 像素生成区
always @(HDMI_X or HDMI_Y) begin
// 小球
if (HDMI_X >= ball_cur_y[16 : 8] && HDMI_X < ball_cur_y[16 : 8] + ball_width
&& HDMI_Y >= ball_cur_x[16 : 8] && HDMI_Y < ball_cur_x[16 : 8] + ball_height) begin
HDMI_Lib_r <= HDMI_Y - ball_cur_x[16 : 8];
HDMI_Lib_c <= HDMI_X - ball_cur_y[16 : 8];
// 挡板
end else if(HDMI_X >= paddle_cur_y && HDMI_X < paddle_cur_y + paddle_width
&& HDMI_Y >= paddle_cur_x && HDMI_Y < paddle_cur_x + paddle_height) begin
HDMI_Lib_r <= HDMI_Y - paddle_cur_x;
HDMI_Lib_c <= HDMI_X - paddle_cur_y;
// 砖块
end else if (HDMI_X >= brick_map_start_y && HDMI_X < brick_map_start_y + brick_width * 14
&& HDMI_Y >= brick_map_start_x && HDMI_Y < brick_map_start_x + brick_height * 8) begin
if (HDMI_Y < brick_map_start_x + brick_height * 1) begin
HDMI_Lib_r <= HDMI_Y - brick_map_start_x;
HDMI_Map_r <= 4'd0;
......
end else if (HDMI_Y < brick_map_start_x + brick_height * 8) begin
HDMI_Lib_r <= HDMI_Y - brick_map_start_x - brick_height * 7;
HDMI_Map_r <= 4'd7;
end

if (HDMI_X < brick_map_start_y + brick_width * 1) begin
HDMI_Lib_c <= HDMI_X - brick_map_start_y;
HDMI_Map_c <= 4'd0;
......
end else if (HDMI_X < brick_map_start_y + brick_width * 14) begin
HDMI_Lib_c <= HDMI_X - brick_map_start_y - brick_width * 13;
HDMI_Map_c <= 4'd13;
end
end else begin
HDMI_Lib_r <= 4'd0;
HDMI_Lib_c <= 4'd0;
HDMI_Map_r <= 4'd0;
HDMI_Map_c <= 4'd0;
end
end

// 根据HDMI坐标位置改变显示的像素的颜色值
always @(HDMI_X or HDMI_Y) begin
if (HDMI_X == 9'd500 || HDMI_Y == 9'd500) begin
{red, green, blue} <= HDMI_COLOR_BACKGROUD;
// 小球
end else if(HDMI_X >= ball_cur_y[16 : 8] && HDMI_X < ball_cur_y[16 : 8] + ball_width
&& HDMI_Y >= ball_cur_x[16 : 8] && HDMI_Y < ball_cur_x[16 : 8] + ball_height) begin
if (HDMI_Type1_Lib[HDMI_pos_1])
{red, green, blue} <= HDMI_COLOR_BALL;
else
{red, green, blue} <= HDMI_COLOR_BACKGROUD;
// 挡板
end else if(HDMI_X >= paddle_cur_y && HDMI_X < paddle_cur_y + paddle_width
&& HDMI_Y >= paddle_cur_x && HDMI_Y < paddle_cur_x + paddle_height) begin
if (HDMI_Type2_Lib[HDMI_pos_2])
{red, green, blue} <= HDMI_COLOR_PADDLE1;
else
{red, green, blue} <= HDMI_COLOR_PADDLE2;
// 砖块
end else if (HDMI_X >= brick_map_start_y && HDMI_X < brick_map_start_y + brick_width * 14
&& HDMI_Y >= brick_map_start_x && HDMI_Y < brick_map_start_x + brick_height * 8) begin
if (brick_exist_map[HDMI_Map_r][HDMI_Map_c]) begin
if (HDMI_Type3_Lib[HDMI_pos_3]) begin
case (HDMI_Map_r)
4'd0: {red, green, blue} <= HDMI_BLUE;
......
4'd7: {red, green, blue} <= HDMI_BLACK;
default:{red, green, blue} <= HDMI_COLOR_BACKGROUD;
endcase
end else
{red, green, blue} <= HDMI_COLOR_BACKGROUD;
end else
{red, green, blue} <= HDMI_COLOR_BACKGROUD;
end else
{red, green, blue} <= HDMI_COLOR_BACKGROUD;
end

9.游戏状态(获胜,失败,正常继续)判断

// 状态9完成标志
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
state9_finish_flag_norm <= 1'b0; // 复位时清除标志
state9_finish_flag_win <= 1'b0;
state9_finish_flag_lose <= 1'b0;
end else if(state == STATE9_Decide_GameSatus && lives == 4'd0) begin
state9_finish_flag_lose <= 1'b1; // 满足条件时设置标志
end else if(state == STATE9_Decide_GameSatus && score == win_score) begin
state9_finish_flag_win <= 1'b1;// 满足条件时设置标志
end else if(state == STATE9_Decide_GameSatus) begin
state9_finish_flag_norm <= 1'b1;
end else begin
state9_finish_flag_norm <= 1'b0;
state9_finish_flag_win <= 1'b0;
state9_finish_flag_lose <= 1'b0; // 其他情况下清除标志
end
end

10.游戏胜利后播放音乐和游戏失败后呼吸灯使能与计数

// 获胜蜂鸣器音乐使能
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
en_buzzer_phoneing <= 1'b0;
else if (state == STATE10_GameOver_Win && state10_finish_flag)
en_buzzer_phoneing <= 1'b1;
else if (cnt_buzzer_phoneing == 9'd500)
en_buzzer_phoneing <= 1'b0;
end

// 获胜后蜂鸣器音乐时间计数
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_buzzer_phoneing <= 9'd0;
else if (state == STATE10_GameOver_Win)
cnt_buzzer_phoneing <= 9'b0;
else if (syn_pos_clk_fps && en_buzzer_phoneing)
cnt_buzzer_phoneing <= cnt_buzzer_phoneing + 9'd1;
else if (~en_buzzer_phoneing)
cnt_buzzer_phoneing <= 9'd0;
end
(cnt_buzzer_phoneing 会影响蜂鸣器声音频率)​

// 失败​同理


FPGA资源占用

开销有点大,这一部分源于我前期项目做的过大的原因。起初还编写了显示字符和汉字的功能,但迫于现实删掉了,但原先留下的框架还在,可能无形增加了LUT占用。


仿真

Quatrus自带的.vwf文件仿真(File -> New -> University Program VWF)

仅仿真GameControl文件:

实物效果图


使用到的官方文件有lab2_electric_piano、lab11_accelerometer、lab13_picture_display、lab14_hdmi

总结感想

这个项目是我于上个学期末,看到数电老师在数电群里发了主办方这么一个活动,由于我数电学的还不错,并且恰巧上学期比较心血来潮,对数字逻辑比较感兴趣,不满足于数电实验上仅仅用FPGA开发板做一套数字钟,于是想着早早下单,寒假酷酷做,结果我还是低估了我自己的毅力,寒假打完美赛队友跑路后就躺了,在家只是简单跑了几个V4.0官方代码样例看看效果。直到开学后才决心动手做这么一个项目。我大一时曾用c++EasyX库和python的pygame库写过几个小游戏,所以当在任务列表看到这个实现BreakOut小游戏顿时心动了,加上HDMI和LCD驱动恰好是我没涉及过的领域,想多尝试,于是才有了这个项目。

这次项目从2月24号下载Lattice Diamond并配置环境到今临近DDL,历时2周,期间也是遇到了各式各样的问题,比如开发板LUT资源太少(2k)以致于我不得不放弃部分代码(原先的项目搭的很大,现在看来纯粹不切实际),专注优化,例如起初我搭了4个除法器实例,但迫于容量限制,优化掉了2个除法器资源,另外两个分时复用,这才勉强不超SLICES(曾一度达到2360/2160)。

这个项目实在辛苦,为了HDMI显示我专门跑到数电老师实验室问学长交流,HDMI拿数据采集器接FPGA板完全采集不到数据,折腾了好一段时间才发现淘宝新买的数据采集器不兼容我的设备。为了这个项目,每天都在做,ddl周周末一天泡12小时在实验室。

不过虽然辛苦,但开发的过程中我还是收获了很多,感觉自己对FPGA开发的理解更深了,对FPGA的并行执行逻辑有了更好的感悟(我原先是做STM32开发的,写FPGA好不适应),同时看到样例里面的代码并学着写才发现同一总时钟控制所有always@()语句的好处(之前我是posedge其他信号与复位信号混着写),并且也挑战了自己的新上限。

总之,对于这次活动我很满意,对自己也很满意。

感谢硬禾为我们提供了这样的活动,扩展自己的知识面,希望今后活动越办越好。


附件下载
基于FPGA的BreakOut游戏LCD与HDMI显示实现.zip
如题
团队介绍
华中科技大学 电子信息与通信学院 唐德伟
团队成员
Anon_Tokoy
评论
0 / 100
查看更多
猜你喜欢
2024寒假练-基于小脚丫FPGA实现秒表该项目使用了Lattice MXO2的小脚丫FPGA核心板 - Type C接口,实现了具有启动、停止、递增和清除功能的秒表的设计,它的主要功能为:使用四个按钮输入:开始、停止、增量和清除(重置)。 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。。
烜月xy
323
2024寒假练-基于小脚丫FPGA实现秒表该项目使用了Lattice MXO2的小脚丫FPGA核心板-Type C接口,Verilog语言,实现了具有启动、停止、递增和清除功能的秒表的设计,它的主要功能为:使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。 秒表使用四个按钮输入:开始、停止、增量和清除(重置)。 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。。
子墨一
348
2024寒假练-基于小脚丫FPGA实现秒表该项目使用了基于Lattice MXO2的小脚丫FPGA核心板 - Type C接口,实现了秒表的设计,它的主要功能为:启动、停止、递增和清除。
夙夜
298
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号