問題描述:在8 個7 段管上顯示HELLO_ _ _(可以顯示下劃線或不亮也可),每隔1 秒鐘,字元序列左移或右移一個七段管的位置。系統外部時鐘50 MHz。左/右移位可以通過一個波動開關sw0 來控制。
分析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次達到將字串寫入暫存器。
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
`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
我們可以清楚的看見,開始時sw0處於高電位, 輸出的高電平從高位移動向低位(七段管上文字向右移動),當 sw0 的值從高電平轉換為低電平時,輸出的高電平從低位移動向高位(七段管上的文字向左移動)。
現在對於我們來說,要向暫存器中寫入「hello_ _ _" 需要使用者知道七段管的表式方法才行,但我們常用的是 ASCLL 碼或者是數值,所以我們可以在寫一個轉化的模組,將使用者傳來的數值或者ascll碼轉化為7短管的顯示陣列,來方便我們的賦值工作。
參考如下: