大家好,欢迎来到IT知识分享网。
目录
vivado工程的链接见文章最后
一、设计概述
通过对三阶魔方的建模,在FPGA上实现一个虚拟魔方,可以通过按键的组合来对模型进行旋转操作,对魔方操作的结果通过HDMI接口显示到显示器上。
开发板:Digilent ZYBO 7020
FPGA型号:ZYNQ xc7z020clg400-2
二、设计模块
1、三阶魔方建模
(1)魔方基本操作:
三阶魔方有六个面,分别是前面(Front)、后面(Behind)、左面(Left)、右面(Right)、上面(Up)、下面(Down),旋转一个面就用对应面的符号替代,这样方便了公式的记忆以及爱好者的交流。 对于魔方爱好者而言,魔方的转动有以下的操作与名称的对应图:
这样,每一种基本旋转就对应了一种符号表示。
本项目正是对三阶魔方的各种基本旋转进行建模,基本的旋转正确,则更为复杂的打乱也是正确的。
(2)魔方建模:
在FPGA这种硬件中色彩是可以使用一种二进制编码来表示的,标准的三阶魔方一共有六种颜色,又因为2^2<6<2^3,因此一种颜色使用一个3位的二进制编码来表示。又因为魔方一个面有9个色块,所以一共需要6(个面)*9(个色块)*3(位编码)=162位来表示一整个魔方的状态。我们所建模的魔方是红前橙后绿左蓝右白顶黄底的,为每一个色块分配一个3位编码来表示其颜色,我们得到了如下展开图:
使用了一个[161:0]的向量来表示整个魔方的状态,每个色块对应的颜色编码与向量索引的关系有了映射。3位颜色编码如下所示:
这样,就把魔方的旋转操作转化为了向量的位拼接运算操作,即每次旋转表征魔方状态的向量都重新赋值一次。例如顺时针90度的状态编码图:
同一个色块上面的索引是移动之前的向量索引,下面的是移动之后的向量索引,对应Verilog代码来说,18中旋转通过一个case语句来实现(KEY_VALUE是按键控制编码,不同的操作对应不同的按键编码,从而实现18种旋转),其代码如下:
这里使用了function来封装每一种操作,这样使得整个代码更加简洁易于维护。操作U对应的function代码如下:
初始状态:
function:
其他17种操作以此类推, 首先确定每一种操作的色块编码向量转移情况,然后再编写对应旋转的函数,这个建模的过程并不难,只是需要耐心和细心才能正确无误的写出每一种函数的状态编码转换。详细代码如下:
`timescale 1ns / 1ps module CUBE( input clk, input rst_n, input [4:0]KEY_Value,//矩阵键盘控制转动 output reg [161:0]cube_state//魔方状态:6*9*3 ); reg [161:0]cube_nstate;//暂存魔方下一个状态 //魔方色块颜色定义 parameter WHITE = 3'b000; parameter GREEN = 3'b001; parameter ORANGE = 3'b010; parameter BLUE = 3'b011; parameter RED = 3'b100; parameter YELLOW = 3'b101; //魔方状态转换 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin cube_state <= { ORANGE,ORANGE,ORANGE,ORANGE,ORANGE,ORANGE,ORANGE,ORANGE,ORANGE,//B YELLOW,YELLOW,YELLOW,YELLOW,YELLOW,YELLOW,YELLOW,YELLOW,YELLOW,//D BLUE,BLUE,BLUE,BLUE,BLUE,BLUE,BLUE,BLUE,BLUE, //R RED,RED,RED,RED,RED,RED,RED,RED,RED, //F GREEN,GREEN,GREEN,GREEN,GREEN,GREEN,GREEN,GREEN,GREEN, //L WHITE,WHITE,WHITE,WHITE,WHITE,WHITE,WHITE,WHITE,WHITE //U }; end else begin cube_state <= cube_nstate; end end //产生魔方下一个状态 always@(*)begin case(KEY_Value) 5'd0 :cube_nstate <= U(cube_state); 5'd1 :cube_nstate <= D(cube_state); 5'd2 :cube_nstate <= F(cube_state); 5'd3 :cube_nstate <= B(cube_state); 5'd4 :cube_nstate <= L(cube_state); 5'd5 :cube_nstate <= R(cube_state); 5'd6 :cube_nstate <= U2(cube_state); 5'd7 :cube_nstate <= D2(cube_state); 5'd8 :cube_nstate <= F2(cube_state); 5'd9 :cube_nstate <= B2(cube_state); 5'd10:cube_nstate <= L2(cube_state); 5'd11:cube_nstate <= R2(cube_state); 5'd12:cube_nstate <= Un(cube_state); 5'd13:cube_nstate <= Dn(cube_state); 5'd14:cube_nstate <= Fn(cube_state); 5'd15:cube_nstate <= Bn(cube_state); 5'd16:cube_nstate <= Ln(cube_state); 5'd17:cube_nstate <= Rn(cube_state); default:cube_nstate <= cube_state; endcase end /*--------------------------转动函数定义-------------------------------*/ function [161:0]U( input [161:0]c ); U = { c[29:27],c[32:30],c[35:33],c[152:150],c[149:147],c[146:144],c[143:141],c[140:138],c[137:135], c[134:108],//D面未动 c[107:105],c[104:102],c[101:99],c[98:96],c[95:93],c[92:90],c[155:153],c[158:156],c[161:159], c[80:78],c[77:75],c[74:72],c[71:69],c[68:66],c[65:63],c[89:87],c[86:84],c[83:81], c[53:51],c[50:48],c[47:45],c[44:42],c[41:39],c[38:36],c[62:60],c[59:57],c[56:54], c[8:6],c[17:15],c[26:24],c[5:3],c[14:12],c[23:21],c[2:0],c[11:9],c[20:18] }; endfunction function [161:0]D( input [161:0]c ); D = { c[161:159],c[158:156],c[155:153],c[152:150],c[149:147],c[146:144],c[101:99],c[104:102],c[107:105], c[116:114],c[125:123],c[134:132],c[113:111],c[122:120],c[131:129],c[110:108],c[119:117],c[128:126], c[80:78],c[77:75],c[74:72],c[98:96],c[95:93],c[92:90],c[89:87],c[86:84],c[83:81], c[53:51],c[50:48],c[47:45],c[71:69],c[68:66],c[65:63],c[62:60],c[59:57],c[56:54], c[137:135],c[140:138],c[143:141],c[44:42],c[41:39],c[38:36],c[35:33],c[32:30],c[29:27], c[26:0]//U面未动 }; endfunction function [161:0]F( input [161:0]c ); F = { c[161:135],//B面未动 c[134:132],c[131:129],c[128:126],c[125:123],c[122:120],c[119:117],c[83:81],c[92:90],c[101:99], c[107:105],c[104:102],c[26:24],c[98:96],c[95:93],c[23:21],c[89:87],c[86:84],c[20:18], c[62:60],c[71:69],c[80:78],c[59:57],c[68:66],c[77:75],c[56:54],c[65:63],c[74:72], c[116:114],c[50:48],c[47:45],c[113:111],c[41:39],c[38:36],c[110:108],c[32:30],c[29:27], c[35:33],c[44:42],c[53:51],c[17:15],c[14:12],c[11:9],c[8:6],c[5:3],c[2:0] }; endfunction function [161:0]B( input [161:0]c ); B = { c[143:141],c[152:150],c[161:159],c[140:138],c[149:147],c[158:156],c[137:135],c[146:144],c[155:153], c[47:45],c[38:36],c[29:27],c[125:123],c[122:120],c[119:117],c[116:114],c[113:111],c[110:108], c[128:126],c[104:102],c[101:99],c[131:129],c[95:93],c[92:90],c[134:132],c[86:84],c[83:81], c[80:54],//F面未动 c[53:51],c[50:48],c[2:0],c[44:42],c[41:39],c[5:3],c[35:33],c[32:30],c[8:6], c[26:24],c[23:21],c[20:18],c[17:15],c[14:12],c[11:9],c[107:105],c[98:96],c[89:87] }; endfunction function [161:0]L( input [161:0]c ); L = { c[161:159],c[158:156],c[128:126],c[152:150],c[149:147],c[119:117],c[143:141],c[140:138],c[110:108], c[134:132],c[131:129],c[74:72],c[125:123],c[122:120],c[65:63],c[116:114],c[113:111],c[56:54], c[107:81],//R面未动 c[80:78],c[77:75],c[20:18],c[71:69],c[68:66],c[11:9],c[62:60],c[59:57],c[2:0], c[35:33],c[44:42],c[53:51],c[32:30],c[41:39],c[50:48],c[29:27],c[38:36],c[47:45], c[26:24],c[23:21],c[155:153],c[17:15],c[14:12],c[146:144],c[8:6],c[5:3],c[137:135] }; endfunction function [161:0]R( input [161:0]c ); R = { c[26:24],c[158:156],c[155:153],c[17:15],c[149:147],c[146:144],c[8:6],c[140:138],c[137:135], c[161:159],c[131:129],c[128:126],c[152:150],c[122:120],c[119:117],c[143:141],c[113:111],c[110:108], c[89:87],c[98:96],c[107:105],c[86:84],c[95:93],c[104:102],c[83:81],c[92:90],c[101:99], c[134:132],c[77:75],c[74:72],c[125:123],c[68:66],c[65:63],c[116:114],c[59:57],c[56:54], c[53:27],//L面未动 c[80:78],c[23:21],c[20:18],c[71:69],c[14:12],c[11:9],c[62:60],c[5:3],c[2:0] }; endfunction function [161:0]U2( input [161:0]c ); U2 = { c[56:54],c[59:57],c[62:60],c[152:150],c[149:147],c[146:144],c[143:141],c[140:138],c[137:135], c[134:108],//D面未动 c[107:105],c[104:102],c[101:99],c[98:96],c[95:93],c[92:90],c[35:33],c[32:30],c[29:27], c[80:78],c[77:75],c[74:72],c[71:69],c[68:66],c[65:63],c[155:153],c[158:156],c[161:159], c[53:51],c[50:48],c[47:45],c[44:42],c[41:39],c[38:36],c[89:87],c[86:84],c[83:81], c[2:0],c[5:3],c[8:6],c[11:9],c[14:12],c[17:15],c[20:18],c[23:21],c[26:24] }; endfunction function [161:0]D2( input [161:0]c ); D2 = { c[161:159],c[158:156],c[155:153],c[152:150],c[149:147],c[146:144],c[74:72],c[77:75],c[80:78], c[110:108],c[113:111],c[116:114],c[119:117],c[122:120],c[125:123],c[128:126],c[131:129],c[134:132], c[53:51],c[50:48],c[47:45],c[98:96],c[95:93],c[92:90],c[89:87],c[86:84],c[83:81], c[137:135],c[140:138],c[143:141],c[71:69],c[68:66],c[65:63],c[62:60],c[59:57],c[56:54], c[107:105],c[104:102],c[101:99],c[44:42],c[41:39],c[38:36],c[35:33],c[32:30],c[29:27], c[26:0]//U面未动 }; endfunction function [161:0]F2( input [161:0]c ); F2 = { c[161:135],//B面未动 c[134:132],c[131:129],c[128:126],c[125:123],c[122:120],c[119:117],c[20:18],c[23:21],c[26:24], c[107:105],c[104:102],c[35:33],c[98:96],c[95:93],c[44:42],c[89:87],c[86:84],c[53:51], c[56:54],c[59:57],c[59:57],c[65:63],c[68:66],c[71:69],c[74:72],c[77:75],c[80:78], c[83:81],c[50:48],c[47:45],c[92:90],c[41:39],c[38:36],c[101:99],c[32:30],c[29:27], c[110:108],c[113:111],c[116:114],c[17:15],c[14:12],c[11:9],c[8:6],c[5:3],c[2:0] }; endfunction function [161:0]B2( input [161:0]c ); B2 = { c[137:135],c[140:138],c[143:141],c[146:144],c[149:147],c[152:150],c[155:153],c[158:156],c[161:159], c[2:0],c[5:3],c[8:6],c[125:123],c[122:120],c[119:117],c[116:114],c[113:111],c[110:108], c[29:27],c[104:102],c[101:99],c[38:36],c[95:93],c[92:90],c[47:45],c[86:84],c[83:81], c[80:54],//F面未动 c[53:51],c[50:48],c[89:87],c[44:42],c[41:39],c[98:96],c[35:33],c[32:30],c[107:105], c[26:24],c[23:21],c[20:18],c[17:15],c[14:12],c[11:9],c[128:126],c[131:129],c[134:132] }; endfunction function [161:0]L2( input [161:0]c ); L2 = { c[161:159],c[158:156],c[74:72],c[152:150],c[149:147],c[65:63],c[143:141],c[140:138],c[56:54], c[134:132],c[131:129],c[20:18],c[125:123],c[122:120],c[11:9],c[116:114],c[113:111],c[2:0], c[107:81],//R面未动 c[80:78],c[77:75],c[155:153],c[71:69],c[68:66],c[146:144],c[62:60],c[59:57],c[137:135], c[29:27],c[32:30],c[35:33],c[38:36],c[41:39],c[44:42],c[47:45],c[50:48],c[53:51], c[26:24],c[23:21],c[128:126],c[17:15],c[14:12],c[119:117],c[8:6],c[5:3],c[110:108] }; endfunction function [161:0]R2( input [161:0]c ); R2 = { c[80:78],c[158:156],c[155:153],c[71:69],c[149:147],c[146:144],c[62:60],c[140:138],c[137:135], c[26:24],c[131:129],c[128:126],c[17:15],c[122:120],c[119:117],c[8:6],c[113:111],c[110:108], c[89:87],c[86:84],c[83:81],c[92:90],c[95:93],c[98:96],c[101:99],c[104:102],c[107:105], c[161:159],c[77:75],c[74:72],c[152:150],c[68:66],c[65:63],c[143:141],c[59:57],c[56:54], c[53:27],//L面未动 c[134:132],c[23:21],c[20:18],c[125:123],c[14:12],c[11:9],c[116:114],c[5:3],c[2:0] }; endfunction function [161:0]Un( input [161:0]c ); Un = { c[83:81],c[86:84],c[89:87],c[152:150],c[149:147],c[146:144],c[143:141],c[140:138],c[137:135], c[134:108],//D面未动 c[107:105],c[104:102],c[101:99],c[98:96],c[95:93],c[92:90],c[62:60],c[59:57],c[56:54], c[80:78],c[77:75],c[74:72],c[71:69],c[68:66],c[65:63],c[35:33],c[32:30],c[29:27], c[53:51],c[50:48],c[47:45],c[44:42],c[41:39],c[38:36],c[155:153],c[158:156],c[161:159], c[20:18],c[11:9],c[2:0],c[23:21],c[14:12],c[5:3],c[26:24],c[17:15],c[8:6] }; endfunction function [161:0]Dn( input [161:0]c ); Dn = { c[161:159],c[158:156],c[155:153],c[152:150],c[149:147],c[146:144],c[47:45],c[50:48],c[53:51], c[128:126],c[131:129],c[110:108],c[131:129],c[122:120],c[113:111],c[134:132],c[125:123],c[116:114], c[137:135],c[140:138],c[143:141],c[98:96],c[95:93],c[92:90],c[89:87],c[86:84],c[83:81], c[107:105],c[104:102],c[101:99],c[71:69],c[68:66],c[65:63],c[62:60],c[59:57],c[56:54], c[80:78],c[77:75],c[74:72],c[44:42],c[41:39],c[38:36],c[35:33],c[32:30],c[29:27], c[26:0]//U面未动 }; endfunction function [161:0]Fn( input [161:0]c ); Fn = { c[161:135],//B面未动 c[134:132],c[131:129],c[128:126],c[125:123],c[122:120],c[119:117],c[53:51],c[44:42],c[35:33], c[107:105],c[104:102],c[110:108],c[98:96],c[95:93],c[113:111],c[89:87],c[86:84],c[116:114], c[74:72],c[65:63],c[56:54],c[77:75],c[68:66],c[59:57],c[80:78],c[71:69],c[62:60], c[20:18],c[50:48],c[47:45],c[23:21],c[41:39],c[38:36],c[26:24],c[32:30],c[29:27], c[101:99],c[92:90],c[83:81],c[17:15],c[14:12],c[11:9],c[8:6],c[5:3],c[2:0] }; endfunction function [161:0]Bn( input [161:0]c ); Bn = { c[155:153],c[146:144],c[137:135],c[158:156],c[149:147],c[140:138],c[161:159],c[152:150],c[143:141], c[89:87],c[98:96],c[107:105],c[125:123],c[122:120],c[119:117],c[116:114],c[113:111],c[110:108], c[8:6],c[104:102],c[101:99],c[5:3],c[95:93],c[92:90],c[2:0],c[86:84],c[83:81], c[80:54],//F面未动 c[53:51],c[50:48],c[134:132],c[44:42],c[41:39],c[131:129],c[35:33],c[32:30],c[128:126], c[26:24],c[23:21],c[20:18],c[17:15],c[14:12],c[11:9],c[29:27],c[38:36],c[47:45] }; endfunction function [161:0]Ln( input [161:0]c ); Ln = { c[161:159],c[158:156],c[20:18],c[152:150],c[149:147],c[11:9],c[143:141],c[140:138],c[2:0], c[134:132],c[131:129],c[155:153],c[125:123],c[122:120],c[146:144],c[116:114],c[113:111],c[137:135], c[107:81],//R面未动 c[80:78],c[77:75],c[128:126],c[71:69],c[68:66],c[119:117],c[62:60],c[59:57],c[110:108], c[47:45],c[38:36],c[29:27],c[50:48],c[41:39],c[32:30],c[53:51],c[44:42],c[35:33], c[26:24],c[23:21],c[74:72],c[17:15],c[14:12],c[65:63],c[8:6],c[5:3],c[56:54] }; endfunction function [161:0]Rn( input [161:0]c ); Rn = { c[134:132],c[158:156],c[155:153],c[125:123],c[149:147],c[146:144],c[116:114],c[140:138],c[137:135], c[80:78],c[131:129],c[128:126],c[71:69],c[122:120],c[119:117],c[62:60],c[113:111],c[110:108], c[101:99],c[92:90],c[83:81],c[104:102],c[95:93],c[86:84],c[107:105],c[98:96],c[89:87], c[26:24],c[77:75],c[74:72],c[17:15],c[68:66],c[65:63],c[8:6],c[59:57],c[56:54], c[53:27],//L面未动 c[161:159],c[23:21],c[20:18],c[152:150],c[14:12],c[11:9],c[143:141],c[5:3],c[2:0] }; endfunction endmodule
2、魔方转动控制:
(1)控制概述:
由于使用的ZYBO开发板只有4个拨码开关和4个独立按键,这对于我们一共18种基本旋转操作来说是不够用的,于是我们制作了一个有6个独立按键的PCB键盘,加上2个拨码开关,以拨码开关控制旋转的角度是顺时针90°、180°还是逆时针90°,以按键控制UDFBLR六个面哪个面被旋转。原理图、PCB、实物图如下:
(2)按键消抖:
由于独立按键种弹簧片的抖动会使电路误触发,因此需要按键消抖模块滤除抖动,这里挖个坑会更新一个按键消抖模块的教程,先放出按键消抖代码:
module KeyPress( input clk, input rst_n, input KEY_IN, output reg KEY_FLAG, output reg KEY_STATE ); reg key_reg_0, key_reg_1;//打两拍异步转同步 reg en_cnt, cnt_full; reg [3:0]state; reg [19:0]cnt; wire flag_H2L, flag_L2H; localparam Key_up = 4'b0001, Filter_Up2Down = 4'b0010, Key_down = 4'b0100, Filter_Down2Up = 4'b1000; //======判断按键输入信号跳变沿========// always @(posedge clk or negedge rst_n) if(!rst_n) begin key_reg_0 <= 1'b0; key_reg_1 <= 1'b0; end else begin key_reg_0 <= KEY_IN; key_reg_1 <= key_reg_0; end assign flag_H2L = key_reg_1 && (!key_reg_0); assign flag_L2H = (!key_reg_1) && key_reg_0; //============计数使能模块==========// always @(posedge clk or negedge rst_n) if(!rst_n) cnt <= 1'b0; else if(en_cnt) cnt <= cnt + 1'b1; else cnt <= 1'b0; //=============计数模块=============// always @(posedge clk or negedge rst_n) if(!rst_n) cnt_full <= 1'b0; else if(cnt == 20'd999_999) cnt_full <= 1'b1; else cnt_full <= 1'b0; //=============有限状态机============// always @(posedge clk or negedge rst_n) if(!rst_n) begin en_cnt <= 1'b0; state <= Key_up; KEY_FLAG <= 1'b0; KEY_STATE <= 1'b1; end else case(state) //保持没按 Key_up: begin KEY_FLAG <= 1'b0; if(flag_H2L) begin state <= Filter_Up2Down; en_cnt <= 1'b1; end else state <= Key_up; end //正在向下按 Filter_Up2Down: begin if(cnt_full) begin en_cnt <= 1'b0; state <= Key_down; KEY_STATE <= 1'b0; KEY_FLAG <= 1'b1; end else if(flag_L2H) begin en_cnt <= 1'b0; state <= Key_up; end else state <= Filter_Up2Down; end //保持按下状态 Key_down: begin KEY_FLAG <= 1'b0; if(flag_L2H) begin state <= Filter_Down2Up; en_cnt <= 1'b1; end else state <= Key_down; end //正在释放按键 Filter_Down2Up: begin if(cnt_full) begin en_cnt <= 1'b0; state <= Key_up; KEY_FLAG <= 1'b1; KEY_STATE <= 1'b1; end else if(flag_H2L) begin en_cnt <= 1'b0; state <= Key_down; end else state <= Filter_Down2Up; end //其他未定义状态 default: begin en_cnt <= 1'b0; state <= Key_up; KEY_FLAG <= 1'b0; KEY_STATE <= 1'b1; end endcase endmodule
(3)魔方控制:
六个独立按键先经过消抖之后产生正确的信号,结合2个拨码开关的状态,使用一个case语句就能实现魔方状态的控制:
每一种组合对应一个KEY_VALUE,然后KEY_VALUE对应魔方模块中一种旋转操作√。完整代码如下:
module cube_ctrl( input clk, input rst_n, input [1:0]rotate_mode,//00->顺90度,01->180度,11->逆90度 input [5:0]key_n, output reg[4:0]KEY_Value ); wire [5:0]KEY_STATE,KEY_FLAG; //模块例化 KeyPress k0( .clk(clk), .rst_n(rst_n), .KEY_IN(key_n[0]), .KEY_FLAG(KEY_FLAG[0]), .KEY_STATE(KEY_STATE[0]) ); KeyPress k1( .clk(clk), .rst_n(rst_n), .KEY_IN(key_n[1]), .KEY_FLAG(KEY_FLAG[1]), .KEY_STATE(KEY_STATE[1]) ); KeyPress k2( .clk(clk), .rst_n(rst_n), .KEY_IN(key_n[2]), .KEY_FLAG(KEY_FLAG[2]), .KEY_STATE(KEY_STATE[2]) ); KeyPress k3( .clk(clk), .rst_n(rst_n), .KEY_IN(key_n[3]), .KEY_FLAG(KEY_FLAG[3]), .KEY_STATE(KEY_STATE[3]) ); KeyPress k4( .clk(clk), .rst_n(rst_n), .KEY_IN(key_n[4]), .KEY_FLAG(KEY_FLAG[4]), .KEY_STATE(KEY_STATE[4]) ); KeyPress k5( .clk(clk), .rst_n(rst_n), .KEY_IN(key_n[5]), .KEY_FLAG(KEY_FLAG[5]), .KEY_STATE(KEY_STATE[5]) ); //KEY_VALUE译码 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin KEY_Value <= 5'd18; end else if(KEY_FLAG != 6'b000000)begin case({rotate_mode,KEY_STATE}) 8'b00_:KEY_Value <= 5'd0; //U 8'b00_:KEY_Value <= 5'd1; //D 8'b00_:KEY_Value <= 5'd2; //F 8'b00_:KEY_Value <= 5'd3; //B 8'b00_:KEY_Value <= 5'd4; //L 8'b00_011111:KEY_Value <= 5'd5; //R 8'b01_:KEY_Value <= 5'd6; //2U 8'b01_:KEY_Value <= 5'd7; //2D 8'b01_:KEY_Value <= 5'd8; //2F 8'b01_:KEY_Value <= 5'd9; //2B 8'b01_:KEY_Value <= 5'd10;//2L 8'b01_011111:KEY_Value <= 5'd11;//2R 8'b11_:KEY_Value <= 5'd12;//U' 8'b11_:KEY_Value <= 5'd13;//D' 8'b11_:KEY_Value <= 5'd14;//F' 8'b11_:KEY_Value <= 5'd15;//B' 8'b11_:KEY_Value <= 5'd16;//L' 8'b11_011111:KEY_Value <= 5'd17;//R' default:KEY_Value <= 5'd18; endcase end else begin KEY_Value <= 5'd18; end end endmodule
3、HDMI显示模块:
本项目使用到了Digilent官方的HDMI接口的IP,视频信号产生的原理与VGA接口相同,将视频信号输入HDMI的驱动IP转化为TMDS信号输出显示到显示器上。这里同样挖一个坑,我会单独出一个HDMI接口驱动的教程,这里仅针对本实验用到的魔方立体图和展开图的绘制做一个说明。
(1)颜色编码转换:
我们在medule cube中使用了一个[161:0]cube_state向量来表示魔方的状态,一个色块采用3位编码来表示其颜色,但是HDMI显示是采用RGB888编码格式的,一种颜色使用(R,G,B)三原色组成,一个颜色通道使用8位二进制数表示2^8=128中颜色深度,因此我们需要将cube中3位颜色编码对RGB888编码进行一个转换,这使用一个函数function来实现:
(2)绘制魔方展开图和立方体:
图像在显示器上的显示是通过行扫描和场扫描来实现的,如果我们要在显示器特定位置显示一个图案,就先得确定图案在显示器上的坐标,通过行扫描、场扫描计数器的方式在特定的像素输出显示特定的颜色,这样就是实现了图案的显示。
我们使用的显示器分辨率为1440*900,水平扫描频率为56KHz,垂直扫描频率60Hz。根据以上信息我们定义了一系列的参数,并使用水平扫描计数器和垂直扫描计数器在特定位置绘制图像,有如下效果:
参数定义如下:
对于行扫描和场扫描,有水平和垂直扫描参数定义如下:
对于魔方展开图来说,其色块边界、大小以及位置定义如下:
对立方体图来说,其色块边界、大小以及位置定义如下:
首先设置寄存器暂存RGB三种颜色的8位编码:,
声明行同步信号发生器和场同步信号发生器:
然后就如同VGA或HDMI协议中绘制图案那样,在行扫描计数器和列扫描计数器达到特定数值,即达到特定像素的时候显示特定的颜色,这通过一系列条件判断语句来实现,具体代码如下:
最后发送VGA输出信号
(这里使用的是VGA显示原理产生数据,在通过HDMI驱动IP来转化位TMDS信号输出)完整代码如下:
module hdmi_data_gen ( // input clk, input rst_n, input pix_clk, input [4:0]KEY_Value,//矩阵键盘控制转动 output [7:0]VGA_R, output [7:0]VGA_G, output [7:0]VGA_B, output VGA_HS, output VGA_VS, output VGA_DE ); /*-------------------3bit色彩编码转RGB888编码---------------------*/ function [23:0]color_rgb( input [2:0]code ); //RGB色彩编码 parameter WHITE = 24'hFFFFFF; parameter GREEN = 24'h008000; parameter ORANGE= 24'hFFA500; parameter BLUE = 24'h0000FF; parameter RED = 24'hFF0000; parameter YELLOW= 24'hFFFF00; parameter BLACK = 24'h000000; //编码转化 case(code) 3'b000: color_rgb = WHITE; 3'b001: color_rgb = GREEN; 3'b010: color_rgb = ORANGE; 3'b011: color_rgb = BLUE; 3'b100: color_rgb = RED; 3'b101: color_rgb = YELLOW; default:color_rgb = BLACK; endcase endfunction //---------------------------------// // 水平扫描参数的设定1280*720 60HZ //--------------------------------// parameter H_Total = 1680;//e parameter H_Sync = 136; //a parameter H_Back = 200; //b parameter H_Active = 1280;//c parameter H_Front = 64; //d parameter H_Start = 336; parameter H_End = 1616; //-------------------------------// // 垂直扫描参数的设定1280*720 60HZ //-------------------------------// parameter V_Total = 828; parameter V_Sync = 3; parameter V_Back = 24; parameter V_Active = 800; parameter V_Front = 1; parameter V_Start = 27; parameter V_End = 827; //行同步信号发生器 reg[11:0] x_cnt; always @(posedge pix_clk) begin if(x_cnt == H_Total) x_cnt <= 1; else x_cnt <= x_cnt + 1; end //场同步信号发生器 reg[11:0] y_cnt; always @(posedge pix_clk) begin if(y_cnt == V_Total) y_cnt <= 1; else if(x_cnt == H_Total) y_cnt <= y_cnt + 1; end reg [10:0]cur_x; reg [10:0]cur_y; always @(posedge pix_clk) begin cur_x <= (x_cnt < H_Sync + H_Back) ? 11'b0 : (x_cnt - H_Sync - H_Back); cur_y <= (y_cnt < V_Sync + V_Back) ? 11'b0 : (y_cnt - V_Sync - V_Back); end wire hs_de = (x_cnt<H_Start)? 0:(x_cnt<=H_End)?1:0;//行消隐控制 wire vs_de = (y_cnt<V_Start)? 0:(y_cnt<=V_End)?1:0;//列消隐控制 /*------------------------------虚拟魔方模块实例化----------------------------*/ wire [2:0]cube[0:5][0:8]; CUBE Cube( .clk(pix_clk), .rst_n(rst_n), .KEY_Value(KEY_Value),//矩阵键盘控制转动 .cube_state({ cube[5][8],cube[5][7],cube[5][6],cube[5][5],cube[5][4],cube[5][3],cube[5][2],cube[5][1],cube[5][0],//B cube[4][8],cube[4][7],cube[4][6],cube[4][5],cube[4][4],cube[4][3],cube[4][2],cube[4][1],cube[4][0],//D cube[3][8],cube[3][7],cube[3][6],cube[3][5],cube[3][4],cube[3][3],cube[3][2],cube[3][1],cube[3][0],//R cube[2][8],cube[2][7],cube[2][6],cube[2][5],cube[2][4],cube[2][3],cube[2][2],cube[2][1],cube[2][0],//F cube[1][8],cube[1][7],cube[1][6],cube[1][5],cube[1][4],cube[1][3],cube[1][2],cube[1][1],cube[1][0],//L cube[0][8],cube[0][7],cube[0][6],cube[0][5],cube[0][4],cube[0][3],cube[0][2],cube[0][1],cube[0][0] //U })//魔方状态:6*9*3 ); //寄存RGB颜色编码 reg [7:0]O_red, O_green, O_blue; // 功能:画出魔方展开图和立体图1024*768--------------------------------- parameter FLAT_LX = 11'd906;//650; parameter FLAT_UY = 11'd430; parameter BLOCK_SIZ = 11'd25; parameter FACE_SIZ = 11'd75; parameter FACE_SIZ_2 = 11'd150; parameter FACE_SIZ_3 = 11'd225; parameter FACE_SIZ_4 = 11'd300; parameter EDGE_COL = 24'h73E68C;//灰边 parameter FLAT_BGC = 24'h000000;//黑色背景 parameter REAL_LX = 11'd356; parameter REAL_UY = 11'd200; parameter REAL_SZ = 11'd40; parameter REAL_SZ_2 = 2*REAL_SZ; parameter REAL_SZ_3 = 3*REAL_SZ; parameter REAL_SZ_4 = 4*REAL_SZ; parameter REAL_SZ_5 = 5*REAL_SZ; parameter REAL_SZ_6 = 6*REAL_SZ; parameter REAL_SZ_7 = 7*REAL_SZ; parameter REAL_SZ_8 = 8*REAL_SZ; parameter REAL_SZ_9 = 9*REAL_SZ; parameter REAL_SZ_11 = 11*REAL_SZ; parameter REAL_SZ_13 = 13*REAL_SZ; parameter REAL_SZ_15 = 15*REAL_SZ; parameter REAL_BGC = 24'h000000;//黑色背景 wire [10:0]tmp_rx; wire [10:0]tmp_ry; wire [10:0]tmp_rxy; assign tmp_rx = cur_x - REAL_LX; assign tmp_ry = cur_y - REAL_UY; assign tmp_rxy = cur_x + cur_y - REAL_LX - REAL_UY; always @(posedge pix_clk) begin // 展开图 if (cur_x >= FLAT_LX && cur_y >= FLAT_UY) begin // 大边框-行 if (cur_y == (FLAT_UY) || cur_y == (FLAT_UY + FACE_SIZ_3) || cur_y == (FLAT_UY + FACE_SIZ_4)) begin {O_red, O_green, O_blue} <= (((cur_x >= FLAT_LX + FACE_SIZ) && (cur_x <= FLAT_LX + FACE_SIZ_2)) ? EDGE_COL : FLAT_BGC); end else if (cur_y == (FLAT_UY + FACE_SIZ) || cur_y == (FLAT_UY + FACE_SIZ_2)) begin {O_red, O_green, O_blue} <= (((cur_x <= FLAT_LX + FACE_SIZ_3)) ? EDGE_COL : FLAT_BGC); end // 大边框-列 else if (cur_x == (FLAT_LX) || cur_x == (FLAT_LX + FACE_SIZ_3)) begin {O_red, O_green, O_blue} <= (((cur_y >= FLAT_UY + FACE_SIZ) && (cur_y <= FLAT_UY + FACE_SIZ_2)) ? EDGE_COL : FLAT_BGC); end else if (cur_x == (FLAT_LX + FACE_SIZ) || cur_x == (FLAT_LX + FACE_SIZ_2)) begin {O_red, O_green, O_blue} <= (((cur_y <= FLAT_UY + FACE_SIZ_4)) ? EDGE_COL : FLAT_BGC); end // 内颜色 else if (cur_y < FLAT_UY + FACE_SIZ) begin if (cur_x > FLAT_LX + FACE_SIZ && cur_x < FLAT_LX + FACE_SIZ_2) begin {O_red, O_green, O_blue} <= color_rgb(cube[0][( (cur_x-(FLAT_LX+FACE_SIZ))/BLOCK_SIZ + (cur_y-(FLAT_UY))/BLOCK_SIZ*3 )]);//U end else begin {O_red, O_green, O_blue} <= FLAT_BGC; end end else if (cur_y < FLAT_UY + FACE_SIZ_2) begin if (cur_x < FLAT_LX + FACE_SIZ) begin {O_red, O_green, O_blue} <= color_rgb(cube[1][( (cur_x-(FLAT_LX))/BLOCK_SIZ + (cur_y-(FLAT_UY+FACE_SIZ))/BLOCK_SIZ*3 )]);//L end else if (cur_x < FLAT_LX + FACE_SIZ_2) begin {O_red, O_green, O_blue} <= color_rgb(cube[2][( (cur_x-(FLAT_LX+FACE_SIZ))/BLOCK_SIZ + (cur_y-(FLAT_UY+FACE_SIZ))/BLOCK_SIZ*3 )]);//F end else if (cur_x < FLAT_LX + FACE_SIZ_3) begin {O_red, O_green, O_blue} <= color_rgb(cube[3][( (cur_x-(FLAT_LX+FACE_SIZ_2))/BLOCK_SIZ + (cur_y-(FLAT_UY+FACE_SIZ))/BLOCK_SIZ*3 )]);//R end else begin {O_red, O_green, O_blue} <= FLAT_BGC; end end else if (cur_y < FLAT_UY + FACE_SIZ_3) begin if (cur_x > FLAT_LX + FACE_SIZ && cur_x < FLAT_LX + FACE_SIZ_2) begin {O_red, O_green, O_blue} <= color_rgb(cube[4][( (cur_x-(FLAT_LX+FACE_SIZ))/BLOCK_SIZ + (cur_y-(FLAT_UY+FACE_SIZ_2))/BLOCK_SIZ*3 )]);//D end else begin {O_red, O_green, O_blue} <= FLAT_BGC; end end else if (cur_y < FLAT_UY + FACE_SIZ_4) begin if (cur_x > FLAT_LX + FACE_SIZ && cur_x < FLAT_LX + FACE_SIZ_2) begin {O_red, O_green, O_blue} <= color_rgb(cube[5][( (cur_x-(FLAT_LX+FACE_SIZ))/BLOCK_SIZ + (cur_y-(FLAT_UY+FACE_SIZ_3))/BLOCK_SIZ*3 )]);//B end else begin {O_red, O_green, O_blue} <= FLAT_BGC; end end else begin {O_red, O_green, O_blue} <= FLAT_BGC; end end // 立体图 else if (cur_x >= REAL_LX && cur_x <= REAL_LX+REAL_SZ_9 && cur_y >= REAL_UY && cur_y <= REAL_UY+REAL_SZ_9) begin if ( ((tmp_rx == 0 || tmp_rx == REAL_SZ_2 || tmp_rx == REAL_SZ_4 || tmp_rx == REAL_SZ_6) && tmp_ry >= REAL_SZ_3) || ((tmp_rx == REAL_SZ_7 || tmp_rx == REAL_SZ_8 || tmp_rx == REAL_SZ_9) && tmp_rxy >= REAL_SZ_9 && tmp_rxy <= REAL_SZ_15) || ((tmp_ry == 0 || tmp_ry == REAL_SZ || tmp_ry == REAL_SZ_2 || tmp_ry == REAL_SZ_3) && tmp_rxy >= REAL_SZ_3 && tmp_rxy <= REAL_SZ_9) || ((tmp_ry == REAL_SZ_5 || tmp_ry == REAL_SZ_7 || tmp_ry == REAL_SZ_9) && tmp_rx <= REAL_SZ_6) || ((tmp_rxy == REAL_SZ_3 || tmp_rxy == REAL_SZ_5 || tmp_rxy == REAL_SZ_7 || tmp_rxy == REAL_SZ_9) && tmp_ry <= REAL_SZ_3) || ((tmp_rxy == REAL_SZ_11 || tmp_rxy == REAL_SZ_13 || tmp_rxy == REAL_SZ_15) && tmp_rx >= REAL_SZ_6) ) begin {O_red, O_green, O_blue} <= EDGE_COL; end else if (tmp_ry < REAL_SZ_3) begin if (tmp_rxy < REAL_SZ_3) begin {O_red, O_green, O_blue} <= REAL_BGC; end else if (tmp_rxy < REAL_SZ_9) begin {O_red, O_green, O_blue} <= color_rgb(cube[0][ (tmp_rxy-REAL_SZ_3)/REAL_SZ_2 + (tmp_ry/REAL_SZ)*3 ]);//U end else begin {O_red, O_green, O_blue} <= color_rgb(cube[3][ (tmp_rx-REAL_SZ_6)/REAL_SZ + (tmp_rxy - REAL_SZ_9)/REAL_SZ_2*3 ]);//R end end else begin if (tmp_rx < REAL_SZ_6) begin {O_red, O_green, O_blue} <= color_rgb(cube[2][ (tmp_rx)/REAL_SZ_2 + (tmp_ry-REAL_SZ_3)/REAL_SZ_2*3 ]);//F end else if (tmp_rxy < REAL_SZ_15) begin {O_red, O_green, O_blue} <= color_rgb(cube[3][ (tmp_rx-REAL_SZ_6)/REAL_SZ + (tmp_rxy - REAL_SZ_9)/REAL_SZ_2*3 ]);//R {O_red, O_green, O_blue} <= color_rgb(cube[3][ (tmp_rx-REAL_SZ_6)/REAL_SZ + (tmp_rxy - REAL_SZ_9)/REAL_SZ_2*3 ]);//R end else begin {O_red, O_green, O_blue} <= REAL_BGC; end end end else begin {O_red, O_green, O_blue} <= REAL_BGC; end end // 功能:画出魔方展开图和立体图----------------------------------------- assign VGA_HS = (x_cnt==12'd0)? 1:(x_cnt<H_Sync)? 0 : 1; assign VGA_VS = (y_cnt==12'd0)? 1:(y_cnt<V_Sync)? 0 : 1; assign VGA_DE = hs_de & vs_de; assign VGA_R = (hs_de & vs_de) ? O_red : 8'h0; assign VGA_G = (hs_de & vs_de) ? O_green: 8'h0; assign VGA_B = (hs_de & vs_de) ? O_blue : 8'h0; endmodule
(4)HDMI驱动IP例化:
不需要配置任何参数,直接例化即可。
(5)HDMI显示顶层模块:
将hdmi_data_gen 产生的信号传输到HDMI的驱动IP中,将TMDS的信号输出。
module HDMI_display( input clk_100M, input rst_n, input [4:0]KEY_Value, output HDMI_CLK_P, output HDMI_CLK_N, output HDMI_D2_P, output HDMI_D2_N, output HDMI_D1_P, output HDMI_D1_N, output HDMI_D0_P, output HDMI_D0_N, output pixclk ); // wire pixclk; wire[7:0] R,G,B; wire HS,VS,DE; hdmi_data_gen u_hdmi_data_gen ( // .clk(clk_100M), .rst_n(rst_n), .KEY_Value(KEY_Value), .pix_clk (pixclk), .VGA_R (R), .VGA_G (G), .VGA_B (B), .VGA_HS (HS), .VGA_VS (VS), .VGA_DE (DE) ); wire serclk; wire lock; wire[23:0] RGB; assign RGB={R,G,B}; HDMI_FPGA_ML_0 u_HDMI ( .PXLCLK_I (pixclk), .PXLCLK_5X_I (serclk), .LOCKED_I (lock), .RST_N (1'b1), .VGA_HS (HS), .VGA_VS (VS), .VGA_DE (DE), .VGA_RGB (RGB), .HDMI_CLK_P (HDMI_CLK_P), .HDMI_CLK_N (HDMI_CLK_N), .HDMI_D2_P (HDMI_D2_P), .HDMI_D2_N (HDMI_D2_N), .HDMI_D1_P (HDMI_D1_P), .HDMI_D1_N (HDMI_D1_N), .HDMI_D0_P (HDMI_D0_P), .HDMI_D0_N (HDMI_D0_N) ); clk_wiz_0 u_clk ( .clk_in1 (clk_100M), .resetn (1'b1), .clk_out1 (pixclk),//74.25MHz .clk_out2 (serclk),//371.25MHz .locked (lock) ); endmodule
4、顶层模块:
在top中将cube_ctrl和HDMI_display实例化,代码如下:
module HDMI_display( input clk_100M, input rst_n, input [4:0]KEY_Value, output HDMI_CLK_P, output HDMI_CLK_N, output HDMI_D2_P, output HDMI_D2_N, output HDMI_D1_P, output HDMI_D1_N, output HDMI_D0_P, output HDMI_D0_N, output pixclk ); // wire pixclk; wire[7:0] R,G,B; wire HS,VS,DE; hdmi_data_gen u_hdmi_data_gen ( // .clk(clk_100M), .rst_n(rst_n), .KEY_Value(KEY_Value), .pix_clk (pixclk), .VGA_R (R), .VGA_G (G), .VGA_B (B), .VGA_HS (HS), .VGA_VS (VS), .VGA_DE (DE) ); wire serclk; wire lock; wire[23:0] RGB; assign RGB={R,G,B}; HDMI_FPGA_ML_0 u_HDMI ( .PXLCLK_I (pixclk), .PXLCLK_5X_I (serclk), .LOCKED_I (lock), .RST_N (1'b1), .VGA_HS (HS), .VGA_VS (VS), .VGA_DE (DE), .VGA_RGB (RGB), .HDMI_CLK_P (HDMI_CLK_P), .HDMI_CLK_N (HDMI_CLK_N), .HDMI_D2_P (HDMI_D2_P), .HDMI_D2_N (HDMI_D2_N), .HDMI_D1_P (HDMI_D1_P), .HDMI_D1_N (HDMI_D1_N), .HDMI_D0_P (HDMI_D0_P), .HDMI_D0_N (HDMI_D0_N) ); clk_wiz_0 u_clk ( .clk_in1 (clk_100M), .resetn (1'b1), .clk_out1 (pixclk),//74.25MHz .clk_out2 (serclk),//371.25MHz .locked (lock) ); endmodule module top( input clk, input rst_n, input [1:0]rotate_mode,//00->顺90度,01->180度,11->逆90度 input [5:0]key_n, output HDMI_CLK_P, output HDMI_CLK_N, output HDMI_D2_P, output HDMI_D2_N, output HDMI_D1_P, output HDMI_D1_N, output HDMI_D0_P, output HDMI_D0_N ); wire [4:0]KEY_Value; //模块实例化 cube_ctrl C_C( .clk(pixclk), .rst_n(rst_n), .rotate_mode(rotate_mode), .key_n(key_n), .KEY_Value(KEY_Value) ); HDMI_display H_D( .clk_100M(clk), .rst_n(rst_n), .KEY_Value(KEY_Value), .HDMI_CLK_P(HDMI_CLK_P), .HDMI_CLK_N(HDMI_CLK_N), .HDMI_D2_P(HDMI_D2_P), .HDMI_D2_N(HDMI_D2_N), .HDMI_D1_P(HDMI_D1_P), .HDMI_D1_N(HDMI_D1_N), .HDMI_D0_P(HDMI_D0_P), .HDMI_D0_N(HDMI_D0_N), .pixclk(pixclk) ); endmodule
三、约束文件
1、键盘的管脚约束
根据ZYBO的开发板手册得到其Pmod接口如下,我们使用的是JE标准接口。
根据原理图找到管脚的位置:
2、HDMI TX端的管脚约束
根据原理图找到HDMI TX端,根据管脚名称找到对应的管脚进行绑定
3、完整约束文件
set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports rst_n] set_property IOSTANDARD LVCMOS33 [get_ports {key_n[5]}] set_property IOSTANDARD LVCMOS33 [get_ports {key_n[4]}] set_property IOSTANDARD LVCMOS33 [get_ports {key_n[3]}] set_property IOSTANDARD LVCMOS33 [get_ports {key_n[2]}] set_property IOSTANDARD LVCMOS33 [get_ports {key_n[1]}] set_property IOSTANDARD LVCMOS33 [get_ports {key_n[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {rotate_mode[1]}] set_property IOSTANDARD LVCMOS33 [get_ports {rotate_mode[0]}] set_property PACKAGE_PIN K17 [get_ports clk] set_property PACKAGE_PIN T16 [get_ports rst_n] set_property PACKAGE_PIN V12 [get_ports {key_n[5]}] set_property PACKAGE_PIN W16 [get_ports {key_n[4]}] set_property PACKAGE_PIN J15 [get_ports {key_n[3]}] set_property PACKAGE_PIN V13 [get_ports {key_n[2]}] set_property PACKAGE_PIN U17 [get_ports {key_n[1]}] set_property PACKAGE_PIN T17 [get_ports {key_n[0]}] set_property PACKAGE_PIN P15 [get_ports {rotate_mode[1]}] set_property PACKAGE_PIN G15 [get_ports {rotate_mode[0]}] set_property -dict {PACKAGE_PIN H17 IOSTANDARD TMDS_33} [get_ports HDMI_CLK_N] set_property -dict {PACKAGE_PIN H16 IOSTANDARD TMDS_33} [get_ports HDMI_CLK_P] set_property -dict {PACKAGE_PIN D20 IOSTANDARD TMDS_33} [get_ports HDMI_D0_N] set_property -dict {PACKAGE_PIN D19 IOSTANDARD TMDS_33} [get_ports HDMI_D0_P] set_property -dict {PACKAGE_PIN B20 IOSTANDARD TMDS_33} [get_ports HDMI_D1_N] set_property -dict {PACKAGE_PIN C20 IOSTANDARD TMDS_33} [get_ports HDMI_D1_P] set_property -dict {PACKAGE_PIN A20 IOSTANDARD TMDS_33} [get_ports HDMI_D2_N] set_property -dict {PACKAGE_PIN B19 IOSTANDARD TMDS_33} [get_ports HDMI_D2_P]
四、综合与上板验证
1、综合
在vivado中进行综合、布局布线结果如下:
2、上板效果
FPGA虚拟三阶魔方(HDMI显示版)
详见视频,工程将上传至github,链接:https://github.com/lionelZhaowy/FPGA-CUBE.git
FPGA-CUBE
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/143637.html