前言
刚开学有点忙,要写专利、写论文,抽空休息写个小模块,正好昆明理工大学数字电子技术实验A(2023级)开始了,用的Altera的FPGA,我也来掺和一脚。
在数字电子技术中,74系列的中规模集成电路都有一个使能端EN,这个端口的作用是使芯片运作,名如其用处。FPGA工程师在设计时一定会为模块添加使能端(我猜的,错了也请工程师前辈指正),来控制模块的运作。
昆明理工大学数字电子技术实验A的实验任务有三个,分别是:1.利用3个拨动开关模拟3位二进制,在DIG1数码管上显示二进制对应字符;2.利用3个拨动开关模拟3位二进制,在二进制对应数码管上显示二进制对应字符;3.设计6选1数据选择器。(对应字符、对应位置即专属参数——昆明理工大学电子技术实验类特色)
这本和使能端的关联度不大,我只是想从简单的模块中加入使能端,并理解其用法。
一、设计
首先分析一下这三个任务,综合起来看其实就是设计三个数据选择器:1.通过设计8位数据选择器,将专属参数中的参数通过DIG1数码管输出;2.通过设计8位数据选择器,将专属参数中的参数通过数码管的位选和段选输出;3.通过设计6位数据选择器,将专属参数中的参数通过数码管输出(当然也可以是其他输出形式)。所以可以明确任务:设计数据选择器。
在学习FPGA(二)中,我已经将数码管点亮的子模块封装起来了,这里将对其进行修改,修改内容包括:1.加入英文字符的数码管显示;2.加入使能端。
在主模块中,完成的内容是拨动开关的选择。三个开关对应3位二进制,即8个数;两个开关对应2位二进制,即4个数。拿出两个拨动开关用于任务的切换,拿出三个开关用于数据选择。
二、实现
首先编写(修改)点亮数码管的子模块,英文字符的点亮规则这里按照昆明理工大学发的对照表来设计,当然如果有其它对应的可以替换。
1.修改seglight.v
由于需要加入使能端,原子模块中直接输出点亮电平将不再适用,转而需要一个中间寄存器,存放点亮电平,而输出的电平则需要使能端与点亮电平的共同作用。具体程序如下。segnum输入0-9即输出数码管段选0-9的8位二进制,65-90即输出ASCII码对应英文字符的数码管段选8位二进制。最底下判断使能端enable的高低电平确定输出值。none的高组态设置是为了保护输出后的外部电路。
module seglight(enable, seg, dig, segnum, dignum);
input enable;
output [7:0] seg;
output [5:0] dig;
input [8:0] segnum;
input [4:0] dignum;
reg [7:0] seg_temp;
reg [5:0] dig_temp;
localparam seg_none = 8'bzzzzzzzz;
localparam seg_0 = 8'b11111100;
localparam seg_1 = 8'b01100000;
localparam seg_2 = 8'b11011010;
localparam seg_3 = 8'b11110010;
localparam seg_4 = 8'b01100110;
localparam seg_5 = 8'b10110110;
localparam seg_6 = 8'b10111110;
localparam seg_7 = 8'b11100000;
localparam seg_8 = 8'b11111110;
localparam seg_9 = 8'b11110110;
localparam seg_A = 8'b11101110;
localparam seg_B = 8'b00111110;
localparam seg_C = 8'b10011100;
localparam seg_D = 8'b01111010;
localparam seg_E = 8'b10011110;
localparam seg_F = 8'b10001110;
localparam seg_G = 8'b10111100;
localparam seg_H = 8'b01101110;
localparam seg_I = 8'b00001000;
localparam seg_J = 8'b01111000;
localparam seg_K = 8'b01011110;
localparam seg_L = 8'b00011100;
localparam seg_M = 8'b10101010;
localparam seg_N = 8'b00101010;
localparam seg_O = 8'b11001110;
localparam seg_P = 8'b11001110;
localparam seg_Q = 8'b11100110;
localparam seg_R = 8'b00001010;
localparam seg_S = 8'b10100110;
localparam seg_T = 8'b00011110;
localparam seg_U = 8'b01111100;
localparam seg_V = 8'b01111110;
localparam seg_W = 8'b10111000;
localparam seg_X = 8'b01010110;
localparam seg_Y = 8'b01110110;
localparam seg_Z = 8'b10010010;
localparam dig_none = 6'bzzzzzz;
localparam dig_1 = 6'b111110;
localparam dig_2 = 6'b111101;
localparam dig_3 = 6'b111011;
localparam dig_4 = 6'b110111;
localparam dig_5 = 6'b101111;
localparam dig_6 = 6'b011111;
always @(*)
begin
case(segnum)
0: seg_temp <= seg_0;
1: seg_temp <= seg_1;
2: seg_temp <= seg_2;
3: seg_temp <= seg_3;
4: seg_temp <= seg_4;
5: seg_temp <= seg_5;
6: seg_temp <= seg_6;
7: seg_temp <= seg_7;
8: seg_temp <= seg_8;
9: seg_temp <= seg_9;
65:seg_temp <= seg_A;
66:seg_temp <= seg_B;
67:seg_temp <= seg_C;
68:seg_temp <= seg_D;
69:seg_temp <= seg_E;
70:seg_temp <= seg_F;
71:seg_temp <= seg_G;
72:seg_temp <= seg_H;
73:seg_temp <= seg_I;
74:seg_temp <= seg_J;
75:seg_temp <= seg_K;
76:seg_temp <= seg_L;
77:seg_temp <= seg_M;
78:seg_temp <= seg_N;
79:seg_temp <= seg_O;
80:seg_temp <= seg_P;
81:seg_temp <= seg_Q;
82:seg_temp <= seg_R;
83:seg_temp <= seg_S;
84:seg_temp <= seg_T;
85:seg_temp <= seg_U;
86:seg_temp <= seg_V;
87:seg_temp <= seg_W;
88:seg_temp <= seg_X;
89:seg_temp <= seg_Y;
90:seg_temp <= seg_Z;
default:seg_temp <= seg_none;
endcase
case(dignum)
1:dig_temp <= dig_1;
2:dig_temp <= dig_2;
3:dig_temp <= dig_3;
4:dig_temp <= dig_4;
5:dig_temp <= dig_5;
6:dig_temp <= dig_6;
default:dig_temp <= dig_none;
endcase
end
assign seg = enable ? seg_temp : seg_none;
assign dig = enable ? dig_temp : dig_none;
endmodule
2.设计数据选择器assignment.v
接下来是数据选择器。首先通过sw拨动开关的变动启动always块的内容(利用case设计的选择)。根据sw的末三位确认选择地址,可以根据专属参数修改case块内的segnum和dignum。比如拨动开关为000时,专属参数为‘A’第二个数码管输出,segnum就应该为65,dignum就应该为2;拨动开关为001时,专属参数为‘C’第四个数码管输出,segnum就应该为67,dignum就应该为4……。具体代码如下(根据专属参数修改case块中的每个segnum和dignum)。
module assignment(sw, dig, seg, enable);
input enable;
input [7:0] sw;
output [7:0] seg;
output [5:0] dig;
reg [8:0] segnum;
reg [4:0] dignum;
wire [7:0] seg_temp;
wire [7:0] dig_temp;
seglight seglight_1(
.enable(enable),
.seg(seg_temp),
.dig(dig_temp),
.segnum(segnum),
.dignum(dignum)
);
assign seg = enable ? seg_temp : 8'bzzzzzzzz;
assign dig = enable ? dig_temp : 6'bzzzzzz;
always @(sw)
begin
case(sw[2:0])
0:
begin
segnum <= 65;
dignum <= 2;
end
1:
begin
segnum <= 67;
dignum <= 4;
end
2:
begin
segnum <= 1;
dignum <= 6;
end
3:
begin
segnum <= 0;
dignum <= 1;
end
4:
begin
segnum <= 8;
dignum <= 3;
end
5:
begin
segnum <= 0;
dignum <= 5;
end
6:
begin
segnum <= 9;
dignum <= 2;
end
7:
begin
segnum <= 90;
dignum <= 4;
end
endcase
end
endmodule
3.扩展数据选择器assignment_n.v
将上述assignment模块复制出两个副本,用作三个不同任务,并修改需要显示的参数,在进行下一步主模块的编写。
4.编写主模块assignment2.v
主模块中,需要实例化上一步完成的三个任务的子模块。由于分三个任务,这里将拨动开关的至高两位作为任务的切换,分别控制对应子模块的使能端。由于使能端失效时,子模块输出的seg和dig是高组态,因此不会影响使能端生效的子模块。具体代码如下。这里引入了使能端wire连线assignment_en,分段控制子模块的运行。
module assignment2(sw, dig, seg, enable, led, key);
input enable;
input [7:0] sw;
output [7:0] seg;
output [5:0] dig;
output [15:0] led;
input [7:0] key;
wire [3:0] assignment_en;
assign assignment_en[1] = sw[6] & sw[7];
assign assignment_en[2] = (sw[6] ^ sw[7]) & (~sw[6]);
assign assignment_en[3] = (sw[6] ^ sw[7]) & (~sw[7]);
assignment_2_1 assignment_1(
.sw(sw),
.dig(dig),
.seg(seg),
.enable(assignment_en[1])
);
assignment_2_2 assignment_2(
.sw(sw),
.dig(dig),
.seg(seg),
.enable(assignment_en[2])
);
assignment_2_3 assignment_3(
.sw(sw),
.dig(dig),
.seg(seg),
.enable(assignment_en[3])
);
endmodule
三、总结
使能端的作用是控制芯片(子模块)的工作状态。在本次实例中,通过对实例化的子模块上加入使能端,将三个任务合并成了一个模块,提高了运行效率(昆工的小伙伴在验收时记得向老师解释这一段)。
在将要封装的子模块中加上使能端时,也要注意其输出状态,防止输出到同一总线又没做好信号的侦听,导致信号扰动。我的理解是使能端控制运行,则输出特定内容;使能端控制禁用,则输出高组态(输出连总线)或保持安全状态(输出不接总线)。