南京大学-数字逻辑与计算机组成实验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%。请不要等到期末月事务繁忙再来后悔谩骂当初的自己;不功利地说,这门课虽然是选修课,但学到的东西和付出的时间并不对等,平时不上课,就纯粹自学自己搞实验作业,我的评价是要么选其他课更好地提高能力,要么直接不选空出时间来享受生活

如果你跑不了了,那么祝你好运:)

posted @   ilfufu  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 本地部署 DeepSeek:小白也能轻松搞定!
· 传国玉玺易主,ai.com竟然跳转到国产AI
· 自己如何在本地电脑从零搭建DeepSeek!手把手教学,快来看看! (建议收藏)
· 我们是如何解决abp身上的几个痛点
· 如何基于DeepSeek开展AI项目
点击右上角即可分享
微信分享提示