動態

詳情 返回 返回

南京大學-數字邏輯與計算機組成實驗2024秋-VGA接口實驗思路(NJU-DLCOE-LAB6) - 動態 詳情

聲明:本博客僅供學習參考,請勿作出直接抄襲等違反學術誠信的行為

實驗環境

  • 軟件: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%。請不要等到期末月事務繁忙再來後悔謾罵當初的自己;不功利地説,這門課雖然是選修課,但學到的東西和付出的時間並不對等,平時不上課,就純粹自學自己搞實驗作業,我的評價是要麼選其他課更好地提高能力,要麼直接不選空出時間來享受生活

如果你跑不了了,那麼祝你好運:)

Add a new 評論

Some HTML is okay.