问题描述:在8 个7 段管上显示HELLO_ _ _(可以显示下划线或不亮也可),每隔1 秒钟,字符序列左移或右移一个七段管的位置。系统外部时钟50 MHz。左/右移位可以通过一个波动开关sw0 来控制。
1、题目解析:
分析1:如何实现这个功能?
我们需要8个7为的寄存器(或者一个7*8 = 56 位的寄存器)来保留"hello_ _ _"
这个字符串,每秒对这8个寄存器里的值做出改变,让这八个七段管的里的值发生阻塞赋值,达到移动目的(如果是7*8的寄存器做一个循环左移/右移)。
分析2:如果实现每一秒进行一次变化?
根据系统外部时钟为50 MHz,我们可以写一个计数器,时钟每变化一个周期就记录一次,一直数到50 M时输出一个电平。或者每数到25M改变一个让一个输出寄存器里的值发生反转,那么对于这个寄存器,他的周期就是1Hz了。
分析3:"hello_ _ _"
字符串怎么输入?
最简单的我们可以定义一个56位的输入,把值一股脑的全部塞进去。当然现实生活中我们往往不会这么高 ,而使用一个7位的寄存器存放要输入的变量,加上一个3(8个7段管 2*3 =8)位的寄存器来指定输入要放入的具体位置,有点类似计算机中的数据总线和地址总线。这样我们要赋值8次达到将字符串写入寄存器。
2、综合代码:
module show_hello(
// 基本控制
input clk50, // clk50 为输入50Mhz系统外部时钟
input reset, // reset 为复位信号
input sw0, // 控制方向变量, 为正值的时候向右变化,为负值的时候向左变化
// 输入控制
input ifinput, // 是否正在输入, 定义为低位时表示正在输入
input[2:0] inp, // input_position 输入的管子位置 2^3 = 8 从0-7 一共表示8个管子
input[6:0] inv, // input_value 输入的字符型
// 输出控制
output reg[55:0] out, // 1-8 一共8个七段管的输出位置(56位)
output reg err // 错误标志位
);
wire clk1; // 接收内部时钟产生的1Hz信号
divclk1hz clock1(
.reset(ifinput), // 如果用户进行了输入,那么我们就重新开始计数
.clk50(clk50), // 传入的基本信号,用于做分频
.clk1(clk1) // 产生的1Hz信号
);
always @(posedge clk1 or negedge ifinput) begin
if (!ifinput) begin // 如果是在输入状态下就进行寄存的复制
case(inp) // 根据用户要求改变的位置进行赋值。
0: out[6:0] <= inv;
1: out[13:7] <= inv;
2: out[20:14] <= inv;
3: out[27:21] <= inv;
4: out[34:28] <= inv;
5: out[41:35] <= inv;
6: out[48:42] <= inv;
7: out[55:49] <= inv;
endcase
end else begin
err=0;
if(sw0) begin // 如果没有输入,并且sw0 是高电平
out <= {out[6:0], out[55:7]};// 就进行向右移动
end else if(!sw0) begin // 如果没有输入,并且sw0 是低电平
out <= {out[48:0],out[55:49]};// 就进行向左移动
end else begin
err=1;
end
end
end
endmodule
/** 分频模块,每数到25M改变一个让一个输出寄存器clk1里的值发生反转 */
module divclk1hz(reset,clk50,clk1);
input clk50,reset; //clk50 为输入50Mhz 信号,reset 为复位信号
output reg clk1; // 新产生的1hz 信号
integer i=0; //50Mhz 频率下,周期计数器
always@(posedge clk50) begin
if(!reset) begin
i=0;
clk1 = 0;
end else begin
if(i==30) begin i=0; clk1=~clk1; // 25000000 这里的i应该=25M,但是为了更方便的展示效果,我将i的值改为了30,这样七段管里的数值会改变的更快。
end
else i=i+1;
end
end
endmodule
3、测试代码:
`timescale 10 ns/ 1 ns
// 注意上面的时间,要 设置最小的时间单位应该为 1/f * 0.5 = 1 / 50M * 0.5 = 10^-8s = 10ns
module show_hello_vlg_tst();
reg clk50;
reg ifinput;
reg [2:0] inp;
reg [6:0] inv;
reg reset;
reg sw0;
wire [55:0] out;
wire err;
show_hello i1 (
.clk50(clk50),
.ifinput(ifinput),
.inp(inp),
.inv(inv),
.out(out),
.reset(reset),
.sw0(sw0),
.err(err)
);
initial begin
// 初始化方向 和 时钟
sw0 = 1; clk50 = 0;
// 给每一个七段管子赋值,这里为了让大家看的清楚,将8个管子的值赋值为了"88 88888","hello__"需要跟改为
/**
1001000
0110000
1110001
1110001
1100010
1110111
1110111
1110111
*/
#00; ifinput = 1; inp = 0; inv=7'b0000000;
#10 ifinput = 0;
#10; ifinput = 1; inp = 1; inv=7'b0000000;
#10 ifinput = 0;
#10; ifinput = 1; inp = 2; inv=7'b1111111;
#10 ifinput = 0;
#10; ifinput = 1; inp = 3; inv=7'b0000000;
#10 ifinput = 0;
#10; ifinput = 1; inp = 4; inv=7'b0000000;
#10 ifinput = 0;
#10; ifinput = 1; inp = 5; inv=7'b0000000;
#10 ifinput = 0;
#10; ifinput = 1; inp = 6; inv=7'b0000000;
#10 ifinput = 0;
#10; ifinput = 1; inp = 7; inv=7'b0000000;
#10 ifinput = 0; #1 ifinput = 1; // 赋值过程结束
// 经过900 个单位时间的等待,观察输出的右移过程
#899 sw0 = 0;
// 再经过600 个单位时间的等待,暂停运行
#600 $stop;
end
always begin
#1 clk50 =~ clk50;
end
endmodule
4、效果展示
我们可以清楚的看见,开始时sw0处于高电位, 输出的高电平从高位移动向低位(七段管上文字向右移动),当 sw0 的值从高电平转换为低电平时,输出的高电平从低位移动向高位(七段管上的文字向左移动)。
4.5 测试代码2
如果你觉得没有输出字符,不怎么方便观察,可以使用以下测试代码。主要思想是使用函数来完成将七段管的数字转换为字符的工作,再由监视器监听七段管寄存器的输出,每当其发生移动就把输出的值显示在Transcript里。
`timescale 10 ns/ 1 ns
module show_hello_vlg_tst();
reg clk50;
reg ifinput;
reg [2:0] inp;
reg [6:0] inv;
reg reset;
reg sw0;
wire [55:0] out;
wire err;
show_hello i1 (
.clk50(clk50),
.ifinput(ifinput),
.inp(inp),
.inv(inv),
.out(out),
.reset(reset),
.sw0(sw0),
.err(err)
);
function[7:0] showChar;
input[6:0] _in;
case(_in)
7'b1001000:showChar ="H";
7'b0110000:showChar ="E";
7'b1100010:showChar ="O";
7'b1110001:showChar ="L";
default: showChar ="_";
endcase
endfunction
initial begin
// 初始化方向 和 时钟
sw0 = 1; clk50 = 0;
// 给每一个七段管子赋值
#00; ifinput = 1; inp = 7; inv=7'b1001000;
#10 ifinput = 0;
#10; ifinput = 1; inp = 6; inv=7'b0110000;
#10 ifinput = 0;
#10; ifinput = 1; inp = 5; inv=7'b1110001;
#10 ifinput = 0;
#10; ifinput = 1; inp = 4; inv=7'b1110001;
#10 ifinput = 0;
#10; ifinput = 1; inp = 3; inv=7'b1100010;
#10 ifinput = 0;
#10; ifinput = 1; inp = 2; inv=7'b1110111;
#10 ifinput = 0;
#10; ifinput = 1; inp = 1; inv=7'b1110111;
#10 ifinput = 0;
#10; ifinput = 1; inp = 0; inv=7'b1110111;
#10 ifinput = 0; #1 ifinput = 1; // 赋值过程结束
// 经过1800 个单位时间的等待,观察输出的右移过程
#1799 sw0 = 0; #1;
// 再经过1600 个单位时间的等待,暂停运行
#1600 $stop;
end
initial $monitor(out,,,"%c %c %c %c %c %c %c %c",
showChar(out[55:49]),
showChar(out[48:42]),
showChar(out[41:35]),
showChar(out[34:28]),
showChar(out[27:21]),
showChar(out[20:14]),
showChar(out[13: 7]),
showChar(out[6: 0])
);
always begin
#1 clk50 =~ clk50;
end
endmodule
5、还可以改进的地方
现在对于我们来说,要向寄存器中写入“hello_ _ _" 需要用户知道七段管的表式方法才行,但我们常用的是 ASCLL 码或者是数值,所以我们可以在写一个转化的模块,将用户传来的数值或者ascll码转化为7短管的显示数组,来方便我们的赋值工作。
参考如下: