聲明:本博客僅供學習參考,請勿作出直接抄襲等違反學術誠信的行為
實驗環境
- 軟件:Vivado 2020.2
- 硬件:Nexys A7-100T開發板
本門課程的實驗環境似乎有兩種,代碼邏輯可能有所不同,請自行注意
實驗主要目標
- 結合鍵盤模塊,按鍵邏輯正常
- 屏幕有顯示
- 在前面的基礎上,實現刪除、退格、清屏等進階操作
- 輸入特殊字符串,按下回車後顯示對應自定義內容
個人思路與實現
前言
首先本次實驗附件實現的內容是正確的,不需要你改動,即vgadraw、vgactrl、vgasim的邏輯沒有問題,不需要你更改。vgactrl中的參數建議改成640*480比例的,後面實驗會方便一點。參數更改如下:
parameter H_Sync_Width = 96;
parameter H_Back_Porche = 48;
parameter H_Active_Pixels = 640;
parameter H_Front_Porch = 16;
parameter H_Totals = 800;
parameter V_Sync_Width = 2;
parameter V_Back_Porche = 33;
parameter V_Active_Pixels = 480;
parameter V_Front_Porch = 10;
parameter V_Totals = 525;
總覽
然後我們先梳理一下給出的實驗文件結構:.coe文件都是靜態圖像部分的,scancode文件是鍵盤部分的,clkgen文件是生成時鐘的模塊(我沒有使用),ASC16是字模文件,vgactrl負責生成同步信號和x軸y軸位置,vgadraw是一個函數,根據vgactrl部分的x軸y軸信息生成顏色信號,vgasim模塊將vgactrl和vgadraw實例化,然後整理後統一輸出VGA的各種信息
梳理完再看實驗要求,我們要實現鍵盤和屏幕的交互,還要實現簡單的偽終端行為,還要實現偽應用功能切換顯示動態圖片/靜態圖片/文字。所以我們的設計思路如下:以xterm為主文件,和屏幕鍵盤交互;先更改移植之前的鍵盤模塊,使之能正常輸出信號;然後設計實現偽終端界面,包括鍵盤信號譯碼、按字母取字模、按字模信號在屏幕上對應位置顯示黑白像素等基礎操作,以及按下del鍵、回車鍵、方向鍵等的進階邏輯;然後設計動態圖像模塊、靜態圖像模塊、文字模塊,將其在xterm中實例化,並設計狀態機實現終端到這些模塊再回到終端的轉換
鍵盤模塊
這個前面的實驗實現過了,所以沒什麼好説的,我的實現如下
module kbd(
input fastclk,
input PS2_CLK,
input PS2_DATA,
output val,
output [7:0] ascii,
output [7:0] ctl,
output c50hz
);
reg [7:0] kb_mem[255:0];
initial
begin
$readmemh("?/scancode.txt", kb_mem, 0, 255);
end
reg [20:0]clk_dis = 0;
reg clk_50hz = 0;
always @(posedge fastclk) begin //100mhz
if (clk_dis >= 1000000) begin //50hz
clk_dis <= 0;
clk_50hz <= ~clk_50hz;
end else begin
clk_dis <= clk_dis + 1;
end
end
reg [3:0]cnt = 0;
reg [7:0]datacur = 0;
reg [7:0]dataprev = 0;
reg [7:0]dataprepre = 0;
reg [7:0]asciicode = 0;
reg [20:0]pressrecord = 0;
always@(negedge PS2_CLK)begin
case(cnt)
0:begin end
1:datacur[0]<=PS2_DATA;
2:datacur[1]<=PS2_DATA;
3:datacur[2]<=PS2_DATA;
4:datacur[3]<=PS2_DATA;
5:datacur[4]<=PS2_DATA;
6:datacur[5]<=PS2_DATA;
7:datacur[6]<=PS2_DATA;
8:datacur[7]<=PS2_DATA;
9:begin end
10:begin
if(datacur == 8'hf0 || datacur == 8'he0)begin end
else begin
pressrecord <= pressrecord + 20'd1;
dataprepre <= dataprev;
dataprev <= datacur;
asciicode <= kb_mem[datacur];
end
end
default:begin end
endcase
if(cnt<=9) begin cnt<=cnt+1; end
else begin cnt<=0; end
end
reg shift_pressed = 0;
reg cap_pressed = 0;
reg [7:0] current_ascii = 0;
reg [7:0] ctlcode = 0;
reg valid = 0;
reg [20:0] recordrecord = 0;
always @(posedge clk_50hz) begin
if(datacur == dataprev && recordrecord != pressrecord && dataprepre != dataprev) begin
end
else if(datacur == dataprev && recordrecord != pressrecord) begin
if(datacur == 8'h58)begin
cap_pressed <= !cap_pressed;
end
else if (datacur == 8'h12 || datacur == 8'h59) begin
shift_pressed <= 1;
end
else begin
shift_pressed <= 0;
end
recordrecord <= pressrecord;
ctlcode <= datacur;
if (cap_pressed&&!shift_pressed || shift_pressed&&!cap_pressed) begin
current_ascii <= asciicode - 8'h20;
end
else begin
current_ascii <= asciicode;
end
valid <= 1;
end
else begin
valid <= 0;
end
end
assign val = valid;
assign ascii = current_ascii;
assign ctl = ctlcode;
assign c50hz = clk_50hz;
endmodule
動態圖像模塊
這塊就是實驗附件給出的vgasim,在xterm中實例化使用即可,vgasim中實例化了vgactrl和vgadraw文件,記得包括進項目文件
靜態圖像模塊
按實驗pdf創建rom的ip核,把你想顯示的coe導入進去,然後模仿vgasim的文件結構實例化vgactrl和ip核即可,記得根據你的coe文件更改addr計算方式
module sim(
input CLk,
input BTNC,
output [3:0] VGA_R,
output [3:0] VGA_G,
output [3:0] VGA_B,
output VGA_HS,
output VGA_VS
);
wire [11:0] cvga_data;
wire cvalid;
VGACtrl svgactl(.pix_x(),.pix_y(),.hsync(VGA_HS),.vsync(VGA_VS),.pix_valid(cvalid),.pix_clk(CLk),.pix_rst(BTNC));
vga_mem my_pic(.clka(CLk),.ena(1'b1),.wea(1'b0),.addra(svgactl.pix_y*640+svgactl.pix_x),.dina(12'd0),.douta(cvga_data));
assign VGA_R=cvga_data[11:8];
assign VGA_G=cvga_data[7:4];
assign VGA_B=cvga_data[3:0];
endmodule
文字模塊
其具體邏輯和偽終端是相似的,其實就是簡化的偽終端,進行初始化後即可進行VGA輸出,具體實現原理可以參考下面偽終端模塊
偽終端邏輯
首先我們需要一塊較大的內存來存儲字母信息,這裏一般有兩種選擇:選用分佈式ram操作方便,但生成比特流文件需要15-30分鐘甚至更長;選用ip核的ram生成比特流速度快,但操作受限。此處我使用的是分佈式的ram,讀者自行取捨
顯存需要進行初始化的操作,若使用ip核可以類比前面的操作,使用coe文件進行初始化;否則需要寫一個initial模塊初始化一下
//示例
reg [7:0] ascii_mem [0:2399]; // 30 * 80個塊
integer initi;
initial begin
ascii_mem[0] = 8'h78; // 'x'
ascii_mem[1] = 8'h74; // 't'
ascii_mem[2] = 8'h65; // 'e'
ascii_mem[3] = 8'h72; // 'r'
ascii_mem[4] = 8'h6d; // 'm'
ascii_mem[5] = 8'h69; // 'i'
ascii_mem[6] = 8'h6e; // 'n'
ascii_mem[7] = 8'h61; // 'a'
ascii_mem[8] = 8'h6c; // 'l'
ascii_mem[9] = 8'h2d; // '-'
ascii_mem[10] = 8'h2d; // '-'
ascii_mem[11] = 8'h2d; // '-'
for(initi = 12;initi < 2400;initi = initi+1)begin
ascii_mem[initi] = 8'h20; // ' '
end
end
reg [12:0] ascii_mem_index = 80;
然後我們示例化鍵盤模塊,按照實驗pdf根據你選擇的分辨率實例化時鐘ip核模塊
wire clk_25mhz;
clk_wiz_0 cw0(
.reset(BTNC),
.clk_in1(CLK100MHZ),
.locked(),
.clk_out1(clk_25mhz)
);
然後就是狀態機和鍵盤操作了,這裏我們以偽終端為主體,先把字母信息以ascii碼形式存下來,後面再根據不同狀態進行選擇VGA輸出,具體大概如下
reg [3:0] mode = 0; // 0 terminal 1 dynamic pic 2 static pic 3 txt 4 clear
integer tp;
always @ (posedge Keybd.c50hz) begin
if(Keybd.val) begin // 鍵盤信號有效
if(mode == 0)begin // 偽終端狀態
if(Keybd.ascii >= 8'h20) begin // 非功能鍵
ascii_mem[ascii_mem_index]<=Keybd.ascii;
ascii_mem_index <= ascii_mem_index +1;
end
else begin // 功能鍵
if(Keybd.ctl == 8'h5a)begin // 按下回車
ascii_mem[ascii_mem_index] <= 8'h20;
if((ascii_mem_index%80) == 7 && ascii_mem[ascii_mem_index - (ascii_mem_index%80)] == 8'h67 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 1] == 8'h72 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 2] == 8'h61 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 3] == 8'h71 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 4] == 8'h68 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 5] == 8'h69 &&ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 6] == 8'h63)begin
mode <= 1; // 動態圖片
end
else if((ascii_mem_index%80) == 5 && ascii_mem[ascii_mem_index - (ascii_mem_index%80)] == 8'h69 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 1] == 8'h6d && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 2] == 8'h61 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 3] == 8'h67 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 4] == 8'h65)begin
mode <= 2; // 靜態圖片
end
else if((ascii_mem_index%80) == 3 && ascii_mem[ascii_mem_index - (ascii_mem_index%80)] == 8'h74 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 1] == 8'h78 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 2] == 8'h74)begin
mode <= 3; // 文章
end
else if((ascii_mem_index%80) == 5 && ascii_mem[ascii_mem_index - (ascii_mem_index%80)] == 8'h63 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 1] == 8'h6c && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 2] == 8'h65 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 3] == 8'h61 && ascii_mem[ascii_mem_index - (ascii_mem_index%80) + 4] == 8'h72)begin
mode <= 4;
ascii_mem_index <= 80;
end
else begin
end
if(ascii_mem_index>=2320)begin // 超過2400容量時重置位置
mode <= 4;
ascii_mem_index <= 80;
end
else begin
ascii_mem_index <= ascii_mem_index - (ascii_mem_index%80) + 80; // 否則轉到下一行就行
end
end
else if(Keybd.ctl == 8'h66 || Keybd.ctl == 8'h71)begin // 按下退格或者del鍵,此處我定義其行為相同,del不具有刪除回車功能
if(ascii_mem_index%80!=0)begin
ascii_mem[ascii_mem_index]<=8'h20;
ascii_mem[ascii_mem_index-1]<=8'h20;
ascii_mem_index <= ascii_mem_index - 1;
end
end
else if(Keybd.ascii == 8'h12)begin // 按下下方向鍵
ascii_mem[ascii_mem_index]<=asre;
ascii_mem_index <= (ascii_mem_index + 80) > 2399 ? ascii_mem_index : ascii_mem_index + 80;
end
else if(Keybd.ascii == 8'h18)begin // 按下上方向鍵
ascii_mem[ascii_mem_index]<=asre;
ascii_mem_index <= (ascii_mem_index - 80) < 80 ? ascii_mem_index : ascii_mem_index - 80;
end
else if(Keybd.ascii == 8'h14)begin // 按下左方向鍵
ascii_mem[ascii_mem_index]<=asre;
ascii_mem_index <= ascii_mem_index % 80 == 0 ? ascii_mem_index : ascii_mem_index - 1;
end
else if(Keybd.ascii == 8'h16)begin // 按下右方向鍵
ascii_mem[ascii_mem_index]<=asre;
ascii_mem_index <= ascii_mem_index % 80 == 79 ? ascii_mem_index : ascii_mem_index + 1;
end
else begin end
end
end
else if(mode == 1 || mode == 2 || mode == 3)begin // 若為其他狀態,按下任意按鍵後回到偽終端
mode <= 0;
end
else if(mode == 4)begin // 若為重置狀態,直接重置
mode <= 0;
ascii_mem_index <= 80;
for(tp = 80;tp<2400;tp = tp+1)begin
ascii_mem[tp]<=8'h20;
end
end
else begin
mode <= 0;
end
end
else begin end
end
上面我們處理的字母的信息,是以ascii碼的形式存在內存中的,現在我們要顯示文字,要按ascii碼找到字模,按字模的信息在對應位置顯示黑白顏色,達到顯示文字的效果。首先我們要讀入字模信息,這裏建議使用二維的寄存器堆[7:0] [0:1535]並更改後面的計算邏輯,使用三維堆大概率報警告無法讀入信息(經驗之談)
reg [7:0] asc96 [0:95] [0:15];
initial begin
$readmemh("?/ASC16-96.txt", asc96);
end
最後就是實例化與選擇輸出階段,對偽終端,我們需要套用一下vgactrl獲取x軸y軸位置信息,算出現在處在哪個塊中,並算出塊內偏移,後面按塊位置取ascii碼,按ascii碼取字模,按偏移算出具體點像素是白還是黑
//mode 0
VGACtrl myctl(
.pix_clk(clk_25mhz),
.pix_rst(BTNC),
.pix_x(),
.pix_y(),
.hsync(),
.vsync(),
.pix_valid()
);
reg [12:0] current_block = 0;
reg [3:0] x_offest = 0;
reg [3:0] y_offest = 0;
always @ (posedge clk_25mhz)begin
current_block <= ((myctl.pix_x - (myctl.pix_x % 8)) / 8) + (5 * (myctl.pix_y - (myctl.pix_y % 16))); //乘以80再除以16即乘5
x_offest <= 7 - (myctl.pix_x % 8);
y_offest <= myctl.pix_y % 16;
end
其他幾個狀態就實例化對應模塊就行
//mode 1
VGASim vgasim(
.Clk(clk_25mhz),
.BTNC(BTNC),
.VGA_R(),
.VGA_G(),
.VGA_B(),
.VGA_HS(),
.VGA_VS()
);
//mode 2
sim sta(
.CLk(clk_25mhz),
.BTNC(BTNC),
.VGA_R(),
.VGA_G(),
.VGA_B(),
.VGA_HS(),
.VGA_VS()
);
//mode 3
text mytdis(
.TCLk(clk_25mhz),
.BTNC(BTNC),
.VGA_R(),
.VGA_G(),
.VGA_B(),
.VGA_HS(),
.VGA_VS()
);
然後按狀態進行選擇VGA信息輸出
// ff和00是黑白
assign VGA_R = (mode == 0 || mode == 4) ? (asc96[ascii_mem[current_block]-8'h20][y_offest][x_offest] ? 8'hff : 8'h00) : (mode == 1 ? vgasim.VGA_R : (mode == 2 ? sta.VGA_R : mytdis.VGA_R));
assign VGA_G = (mode == 0 || mode == 4) ? (asc96[ascii_mem[current_block]-8'h20][y_offest][x_offest] ? 8'hff : 8'h00) : (mode == 1 ? vgasim.VGA_G : (mode == 2 ? sta.VGA_G : mytdis.VGA_G));
assign VGA_B = (mode == 0 || mode == 4) ? (asc96[ascii_mem[current_block]-8'h20][y_offest][x_offest] ? 8'hff : 8'h00) : (mode == 1 ? vgasim.VGA_B : (mode == 2 ? sta.VGA_B : mytdis.VGA_B));
assign VGA_HS = (mode == 0 || mode == 4) ? myctl.hsync : (mode == 1 ? vgasim.VGA_HS : (mode == 2 ? sta.VGA_HS : mytdis.VGA_HS));
assign VGA_VS = (mode == 0 || mode == 4) ? myctl.vsync : (mode == 1 ? vgasim.VGA_VS : (mode == 2 ? sta.VGA_VS : mytdis.VGA_VS));
附
當時做這個實驗的時候感覺十分噁心,因為一路做過來,難度在此突增,而且我使用分佈式寄存器堆導致跑比特流慢的誇張,一旦哪裏寫錯了屏幕上一點顯示都沒有,心態極其容易爆炸。所以當時做完之後就想着要寫篇博客幫助後來人,雖然不一定有人讀(QAQ)。過完年總算有點時間寫了,卻又發現忘得差不多了,看着之前寫的代碼慢慢回憶,寫下這篇不是很盡善盡美的文章,希望對你有所幫助
如果你選了這門課而且還有退課機會,請快跑。功利地説,前面這些實驗只佔比60%,最後一個難如登天的大實驗佔比40%。請不要等到期末月事務繁忙再來後悔謾罵當初的自己;不功利地説,這門課雖然是選修課,但學到的東西和付出的時間並不對等,平時不上課,就純粹自學自己搞實驗作業,我的評價是要麼選其他課更好地提高能力,要麼直接不選空出時間來享受生活
如果你跑不了了,那麼祝你好運:)