• 【黑金原创教程】【FPGA那些事儿驱动篇I 】实验十一:PS/2模块⑤ — 扩展鼠标


    实验十一:PS/2模块⑤ — 扩展鼠标

    当普通鼠标即三键鼠标再也无法满足需求的时候,扩展鼠标即滚轮鼠标就诞生了,然而实验十一的实验目的就是实现滚轮鼠标的驱动。不过,进入整体之前,先让我们来了解一下鼠标的常用命令。

    clip_image002

    图11.1 命令F3,设置采样频率。

    命令F3也是Set Sample Rate,主要是用来设置采集频率。笔者曾经说过,采集频率就是鼠标采集按键状况还有位置状况的间隔时间,默认下是100次/秒。如图11.1所示,FPGA先发送命令数据8’hF3,事后鼠标会反馈8’hFA以示接收成功,余下FPGA再发送参数数据8’d200,鼠标接收成功后也会反馈 8’hFA。如此一来,鼠标的采集频率从原本的 100次/秒,变成 200次/秒。

    clip_image004

    图11.2 命令E8,设置分辨率。

    命令E8也是 Set Resolution,主要是用来设置分辨率。所谓分辨率就是位置对应寄存器计数的单位,默认下是4计数/mm,亦即 1mm 的距离,鼠标计数4下。如图11.2所示,FPGA先发送命令数据 8’hE8,鼠标接收以后便反馈 8’hFA,FPGA随之也会发送参数数据 8’h01,鼠标接收以后也会反馈数据 8’hFA。完后,鼠标的分辨从原本的 4计数/mm 变成 2计数/mm。

    参数数据所对应的分辨率如表11.1所示:

    表11.1 参数数据所对应的分辨率。

    参数数据

    分辨率

    8’h00

    1计数/mm

    8’h01

    2计数/mm

    8’h02

    4计数/mm

    8’h03

    8计数/mm

    clip_image006

    图11.3 命令F6,使用默认参数。

    假设笔者手痒,不小心打乱鼠标内部的参数数据,此刻笔者可以发送命令F6,即Set Defaults将参数数据回复成原来的缺省值。如图11.3所示,FPGA先发送命令数据8’hF6

    ,鼠标完成接收以后便会反馈8’hFA。

    clip_image008

    图11.4 命令F4使能报告,命令F5关闭报告。

    PS/2鼠标不像PS/2键盘,上电并且完成初始化以后它便会陷入发呆状态,如果不发送命令数据8’hF4(即Enable Data Report)手动开启鼠标的水龙头,鼠标是不会发送报告(即夹杂按键状况与位置状况的数据)。如图11.4所示,FPGA先发送命令数据8’hF4,鼠标接收以后便会反馈8’hFA,事后鼠标立即处于就绪状态,一旦按键状况或者位置状况发生改变,鼠标就会发送报告。

    假设读者觉得鼠标太唠叨,什么大事小事都报告,笔者可以发送命令数据 8’hF5(即 Disable Data Report)为了使其闭嘴。如图11.4所示,FPGA先发送命令数据 8’hF4,鼠标接收完毕以后便会反馈8’hFA,事后鼠标就成为闭嘴状态,大事小事再也不会烦人。如果读者觉得寂寞,读者可以再度发送命令数据 8’hF4,让鼠标再度唱歌。

    clip_image010

    图11.5 命令F2,读取鼠标ID。

    为了区分鼠标是普通鼠标还是扩展鼠标,期间我们必须使用命令8’hF2,即 Get Device ID。如图11.5所示,FPGA发送命令数据 8’hF2,鼠标接收以后先反馈 8’hFA,再来便发送鼠标ID。如果内容是8’h00,则表示该鼠标只是普通鼠标 ... 反之,如果内容是 8’h03,那么该鼠标就是扩展鼠标。因为如此,我们需要更改一下伪函数,结果如代码11.1所示:

    1.             32: // Press low PS2_CLK 100us
    2.            if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
    3.            else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
    4.                          
    5.            33: // release PS2_CLK and set in ,PS2_DAT set out
    6.            begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
    7.                          
    8.            34: // start bit 1
    9.            begin rDAT <= 1'b0; i <= i + 1'b1; end
    10.                          
    11.            35,36,37,38,39,40,41,42,43:  // data bit 9
    12.            if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
    13.                          
    14.            44: // stop bit 1
    15.            if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
    16.                          
    17.            45: // Ack bit
    18.            if( isH2L ) begin i <= i + 1'b1; end
    19.                          
    20.            46: // PS2_DAT set in
    21.            begin isQ2 <= 1'b0; i <= i + 1'b1; end
    22.                          
    23.            /***********/ // Receive 1st Frame
    24.                         
    25.            47,48,49,50,51,52,53,54,55,56,57: // Ingnore 
    26.            if( isH2L ) i <= i + 1'b1;
    27.                          
    28.             58: // Check comd F2
    29.             if( T[7:0] == 8'hF2 ) i <= i + 1'b1;
    30.             else i <= Go;
    31.                          
    32.             /***********/ // Receive 2nd Frame
    33.                          
    34.             59:  // Start bit 1
    35.             if( isH2L ) i <= i + 1'b1; 
    36.                          
    37.             60,61,62,63,64,65,66,67,68: // Data bit 9
    38.             if( isH2L ) begin T[i-60] <= PS2_DAT; i <= i + 1'b1; end
    39.                          
    40.             69: // Stop bit 1
    41.             if( isH2L ) i <= Go;

    代码11.1

    如代码11.1所示,步骤32~57则是发送一帧数据又忽略一帧反馈,基本上与实验十一模一样。至于第58行则是用来判断,FPGA所发送的命令是否是 8’hF2即 Get Device ID

    ?如果是,步骤则继续读取操作,因为命令8’hF2令鼠标反馈8’hFA之余,还会导致鼠标会发送一帧ID数据。否则的话,即表示其他命令,步骤返回。步骤59~69是用来读取下一帧ID数据,期间步骤60~68用来读取 8位数据位,还有1位校验位。完后,步骤便返回。

    小时候的笔者很爱假扮刺客,笔者与近邻的小孩就总是瞎着玩,其它小朋友则扮演秘密商人。刺客为了与秘密商人进行交易,两者之间必须经过暗语核对,例如:

    “阳光的男孩赤裸裸 ... ”,对方问道。

    “对面的女来看过来 ... ”,笔者答道。

    滚轮鼠标也是扩展鼠标,上电以后也不会立即变成扩展鼠标,如果扩展鼠标不经过核对暗语,扩展鼠标也是一只普通的3键鼠标而已 ... 反之,如果完成暗语核对,扩展鼠标才会发挥滚轮功能。

    clip_image012

    图11.6 设置扩展鼠标的暗语。

    如图11.6所示,那是设置扩展鼠标的暗语:

    发送命令数据 8’hF3,接收反馈8’hFA,再发送参数数据 8’hC8,在接收反馈8’hFA;

    发送命令数据 8’hF3,接收反馈8’hFA,再发送参数数据 8’h64,在接收反馈8’hFA;

    发送命令数据 8’hF3,接收反馈8’hFA,再发送参数数据 8’h50,在接收反馈8’hFA;

    发送命令数据 8’hF2,接收反馈8’hFA,再接收鼠标ID8’h03。

    完后,鼠标便成为扩展鼠标,内部也自动初始化,然后进入默认模式。

    clip_image014

    图11.7 扩展鼠标标示的位置。

    普通鼠标相较扩展鼠标,它多了滚轮功能,即鼠标除了标示左键,中键,右键,X还有Y以外,扩展还会标示Z。如图11.7所示,X与Y可以看成面积,至于Z则可以看成上下。当鼠标向西移动,X呈现正直,反之负值;当鼠标向北移动,Y呈现正直,反之负值;当滚动向下活动,Z呈现正直,反之负值。

    clip_image016

    图11.8 扩展鼠标的报告长度。

    为此,扩展鼠标相较普通鼠标,报告长度则多了一个字节。如图11.8所示,当鼠标察觉变化以后,鼠标便会发送4个字节长度的报告,然而字节之间的位分配如表11.1所示:

    表11.1 Device ID 为 8’h03 的报告内容。

    字节/位

    [7]

    [6]

    [5]

    [4]

    [3]

    [2]

    [1]

    [0]

    字节一

    Y溢出位

    X溢出位

    Y[8]符号位

    X[8]符号位

    保留

    中键

    右键

    左键

    字节二

    X[7:0]

    字节三

    Y[7:0]

    字节四

    保留

    保留

    保留

    保留

    Z[3]符号位

    Z[2]

    Z[1]

    Z[0]

    笔者需要补充一下 ... 由于早期Intel 称王,所以扩展鼠标标准都是Intel说话算话,Device ID 为 8’h03 就是其中一种扩展标准。如表11.1所示,字节一至字节三基本上变化不大,反之字节四则稍微不同。字节四的[2..0]位是 Z[2:0],字节四的[3]是Z[3],也是Z的符号位。换句话说,寄存器Z有4位,内容用补码表示。

    clip_image018

    图11.9 扩展鼠标的位置范围。

    图11.9表示扩展鼠标的位置范围,X与Y与普通鼠标一样,Z比较畸形一点,因为Z向上不是正直而是负值,反之亦然。Z的有效范围是 4’b1001~4’b0111或者 -7~7,也就是说滚轮向下活动,寄存器Z就递增,向上滚动,寄存器Z就递减。

    上述内容理解完毕以后,我们便可以开始建模了:

    clip_image020

    图11.10 实验十一的建模图。

    如图11.10所示,组合模块 ps2_demo 包含的内容与实验十相差不了多少,不过却少了正直化的即时操作。期间,PS/2初始化功能模块的 oEn 有两位,oEn[1] 拉高表示鼠标为扩展鼠标,oEn[0] 拉高表示鼠标为普通鼠标。PS/2读取功能模块的 oData[2:0] 直接驱动LED资源, oData[27:4]则驱动数码管基础模块的 iData。

    ps2_init_funcmod.v

    clip_image022

    图11.11 PS/2初始化功能模块的建模图。

    如图11.11所示,PS/2初始化功能模块有两位oEn,[1]拉高表示鼠标为扩展鼠标,[0]拉高则表示鼠标为普通鼠标。

    1.    module ps2_init_funcmod
    2.    (
    3.         input CLOCK, RESET,
    4.         inout PS2_CLK, 
    5.         inout PS2_DAT,
    6.         output [1:0]oEn
    7.    );  
    8.        parameter T100US = 13'd5000;
    9.        parameter FF_Write = 7'd32;

    以上内容为相关的出入端声明。第8行是100us的常量声明,第9行则是伪函数的入口地址。

    11.         /*******************************/ // sub1
    12.         
    13.        reg F2,F1; 
    14.         
    15.        always @ ( posedge CLOCK or negedge RESET )
    16.             if( !RESET )
    17.                  { F2,F1 } <= 2'b11;
    18.              else 
    19.                  { F2, F1 } <= { F1, PS2_CLK };
    20.    
    21.         /*******************************/ // core
    22.         
    23.         wire isH2L = ( F2 == 1'b1 && F1 == 1'b0 );

    以上内容是用来检测电平变化的周边操作,第23行则是下降沿的即时声明。

    24.         reg [8:0]T;
    25.         reg [6:0]i,Go;
    26.         reg [12:0]C1;
    27.         reg rCLK,rDAT;
    28.         reg isQ1,isQ2,isEx;
    29.         reg [1:0]isEn;
    30.         
    31.         always @ ( posedge CLOCK or negedge RESET )
    32.             if( !RESET )
    33.                  begin
    34.                         T <= 9'd0;
    35.                         C1 <= 13'd0;
    36.                         { i,Go } <= { 7'd0,7'd0 };
    37.                         { rCLK,rDAT } <= 2'b11;
    38.                         { isQ1,isQ2,isEx } <= 3'b000;
    39.                         isEn <= 2'b00;
    40.                    end
    41.               else  

    以上内容是相关的寄存器声明,第33~39行则是这群寄存器的复位操作。其中isEn有两位,isEx为扩展鼠标的立旗。

    42.                    case( i )
    43.                     
    44.                         /***********/ // INIT Mouse 
    45.                          
    46.                          0: // Send F3  1111_0011
    47.                          begin T <= { 1'b1, 8'hF3 }; i <= FF_Write; Go <= i + 1'b1; end
    48.                          
    49.                          1: // Send C8  1100_1000
    50.                          begin T <= { 1'b0, 8'hC8 }; i <= FF_Write; Go <= i + 1'b1; end
    51.                          
    52.                          2: // Send F3 1111_0011
    53.                          begin T <= { 1'b1, 8'hF3 }; i <= FF_Write; Go <= i + 1'b1; end
    54.                          
    55.                          3: // Send 64 0110_1000
    56.                          begin T <= { 1'b0, 8'h64 }; i <= FF_Write; Go <= i + 1'b1; end
    57.                          
    58.                          4: // Send F3 1111_0011
    59.                          begin T <= { 1'b1, 8'hF3 }; i <= FF_Write; Go <= i + 1'b1; end
    60.                          
    61.                          5: // Send 50 0101_0000
    62.                          begin T <= { 1'b1, 8'h50 }; i <= FF_Write; Go <= i + 1'b1; end
    63.                          
    64.                          6: // Send F2  1111_0010
    65.                          begin T <= { 1'b0, 8'hF2 }; i <= FF_Write; Go <= i + 1'b1; end
    66.                          
    67.                          7: // Check Mouse ID 00(normal), 03(extend)
    68.                          if( T[7:0] == 8'h03 ) begin isEx <= 1'b1; i <= i + 1'b1; end
    69.                          else if( T[7:0] == 8'h00 ) begin isEx <= 1'b0; i <= i + 1'b1; end
    70.                        
    71.                          8: // Send F4 1111_0100
    72.                          begin T <= { 1'b0, 8'hF4 }; i <= FF_Write; Go <= i + 1'b1; end
    73.                          
    74.                          9:
    75.                          if( isEx ) isEn[1] <= 1'b1;
    76.                          else if( !isEx ) isEn[0] <= 1'b1;
    77.                          

    以上内容是核心操作。步骤0~9是主操作,步骤0~6则是发送用来开启扩展鼠标的暗语,步骤7用来判断鼠标返回的 Device ID 是否为 8’h03,如果是 isEx 立旗,否则 isEx 消除立旗。步骤8用来使能鼠标。步骤9根据 isEx 的状态再来决定 isEn的结果, 如果isEx为1 isEn[1] 便拉高,否则 isEx 拉高,完后步骤停留。

    78.                          /****************/ // PS2 Write Function
    79.                          
    80.                          32: // Press low PS2_CLK 100us
    81.                          if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
    82.                          else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
    83.                          
    84.                          33: // release PS2_CLK and set in ,PS2_DAT set out
    85.                          begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
    86.                          
    87.                          34: // start bit 1
    88.                          begin rDAT <= 1'b0; i <= i + 1'b1; end
    89.                          
    90.                          35,36,37,38,39,40,41,42,43:  // data bit 9
    91.                          if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
    92.                          
    93.                          44: // stop bit 1
    94.                          if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
    95.                          
    96.                          45: // Ack bit
    97.                          if( isH2L ) begin i <= i + 1'b1; end
    98.                          
    99.                          46: // PS2_DAT set in
    100.                          begin isQ2 <= 1'b0; i <= i + 1'b1; end
    101.                          
    102.                          /***********/ // Receive 1st Frame
    103.                         
    104.                          47,48,49,50,51,52,53,54,55,56,57: // Ingnore 
    105.                          if( isH2L ) i <= i + 1'b1;
    106.                          
    107.                          58: // Check comd F2
    108.                          if( T[7:0] == 8'hF2 ) i <= i + 1'b1;
    109.                          else i <= Go;
    110.                          

    以上内容是部分核心操作。步骤32~58是部分伪函数,内容则是发送一帧数据,再读取一帧反馈,完后便进入步骤58判断,发送的命令是否为 8’hF2,如果是便继续步骤,否则便返回步骤。

    111.                          /***********/ // Receive 2nd Frame
    112.                          
    113.                          59:  // Start bit 1
    114.                          if( isH2L ) i <= i + 1'b1; 
    115.                          
    116.                          60,61,62,63,64,65,66,67,68: // Data bit 9
    117.                          if( isH2L ) begin T[i-60] <= PS2_DAT; i <= i + 1'b1; end
    118.                          
    119.                          69: // Stop bit 1
    120.                          if( isH2L ) i <= Go;
    121.                                              
    122.                     endcase
    123.         

    以上内容是部分核心操作。步骤59~69也是部分伪函数,主要用来读取下一帧数据的字节内容,在此是针对命令8’hF2,也就是Device ID。读完一帧数据以后便返回步骤。

    124.         assign PS2_CLK = isQ1 ? rCLK : 1'bz;
    125.         assign PS2_DAT = isQ2 ? rDAT : 1'bz;
    126.         assign oEn = isEn;
    127.      
    128.    endmodule

    以上内容为驱动输出声明。

    ps2_read_funcmod.v

    clip_image024

    图11.12 PS/2读功能模块的建模图。

    实验十一的PS/2读功能模块与实验十相比,左边的 iEn出入多出一位以外,右边的oData也多出一个字节。

    1.    module ps2_read_funcmod
    2.    (
    3.         input CLOCK, RESET,
    4.         input PS2_CLK,PS2_DAT,
    5.         input [1:0]iEn,
    6.         output oTrig,
    7.         output [31:0]oData
    8.    );  
    9.         parameter FF_Read = 7'd32;

    以上内容是相关的出入端声明。第9行是伪函数的入口。

    10.    
    11.         /*******************************/ // sub1
    12.         
    13.        reg F2,F1; 
    14.         
    15.        always @ ( posedge CLOCK or negedge RESET )
    16.             if( !RESET )
    17.                  { F2,F1 } <= 2'b11;
    18.              else 
    19.                  { F2, F1 } <= { F1, PS2_CLK };
    20.    
    21.         /*******************************/ // core
    22.         
    23.         wire isH2L = ( F2 == 1'b1 && F1 == 1'b0 );

    以上内容是检测电平变化的周边操作,第23行则是下降沿的即时声明。

    24.         reg [31:0]D1;
    25.         reg [7:0]T;
    26.         reg [6:0]i,Go;
    27.         reg isDone;
    28.         
    29.         always @ ( posedge CLOCK or negedge RESET )
    30.             if( !RESET )
    31.                  begin
    32.                         D1 <= 32'd0;
    33.                         T <= 8'd0;
    34.                         { i,Go } <= { 7'd0,7'd0 };
    35.                         isDone <= 1'b0;
    36.                    end

    以上内容为相关的寄存器声明以及复位操作。

    37.               else if( iEn[1] )  
    38.                    case( i )
    39.                     
    40.                         /***********/ // Extend Mouse Read Data 
    41.                          
    42.                          0: // Read Data 1st byte
    43.                          begin i <= FF_Read; Go <= i + 1'b1; end
    44.                          
    45.                          1: // Store Data 1st byte
    46.                          begin D1[7:0] <= T; i <= i + 1'b1; end
    47.                          
    48.                          2: // Read Data 2nd byte
    49.                          begin i <= FF_Read; Go <= i + 1'b1; end
    50.                          
    51.                          3: // Store Data 2nd byte
    52.                          begin D1[15:8] <= T; i <= i + 1'b1; end
    53.                          
    54.                          4: // Read Data 3rd byte
    55.                          begin i <= FF_Read; Go <= i + 1'b1; end
    56.                          
    57.                          5: // Store Data 3rd byte
    58.                          begin D1[23:16] <= T; i <= i + 1'b1; end
    59.                          
    60.                          6: // Read Data 4rd byte
    61.                          begin i <= FF_Read; Go <= i + 1'b1; end
    62.                          
    63.                          7: // Store Data 4rd byte
    64.                          begin D1[31:24] <= T; i <= i + 1'b1; end
    65.                          
    66.                          8:
    67.                          begin isDone <= 1'b1; i <= i + 1'b1; end
    68.                          
    69.                          9:
    70.                          begin isDone <= 1'b0; i <= 7'd0; end

    以上内容为部分核心操作。第37行的 if( iEn[1] ) 表示下面所有内容都是扩展鼠标的核心操作。步骤0~7则是读取4个字节的数据,步骤8~9用来产生完成信号以示一次性的报告已经接收完毕。

    71.                          
    72.                          /****************/ // PS2 Write Function
    73.                          
    74.                          32: // Start bit
    75.                          if( isH2L ) i <= i + 1'b1; 
    76.                          
    77.                          33,34,35,36,37,38,39,40:  // Data byte 
    78.                          if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1;  end
    79.                          
    80.                          41: // Parity bit
    81.                          if( isH2L ) i <= i + 1'b1;
    82.                          
    83.                          42: // Stop bit
    84.                          if( isH2L ) i <= Go;
    85.                          
    86.                     endcase

    以上内容为部分核心操作。步骤32~42是读取一帧数据的伪函数。

    87.                else if( iEn[0] )  
    88.                    case( i )
    89.                     
    90.                         /***********/ // Normal Mouse Read Data  
    91.                          
    92.                          0: // Read Data 1st byte
    93.                          begin i <= FF_Read; Go <= i + 1'b1; end
    94.                          
    95.                          1: // Store Data 1st byte
    96.                          begin D1[7:0] <= T; i <= i + 1'b1; end
    97.                          
    98.                          2: // Read Data 2nd byte
    99.                          begin i <= FF_Read; Go <= i + 1'b1; end
    100.                          
    101.                          3: // Store Data 2nd byte
    102.                          begin D1[15:8] <= T; i <= i + 1'b1; end
    103.                          
    104.                          4: // Read Data 3rd byte
    105.                          begin i <= FF_Read; Go <= i + 1'b1; end
    106.                          
    107.                          5: // Store Data 3rd byte
    108.                          begin D1[23:16] <= T; i <= i + 1'b1; end
    109.                          
    110.                          6:
    111.                          begin isDone <= 1'b1; i <= i + 1'b1; end
    112.                          
    113.                          7:
    114.                          begin isDone <= 1'b0; i <= 7'd0; end
    115.                          

    以上内容为部分核心操作。第87行的 if( iEn[0] ) 表示下面的内容均为普通鼠标的核心操作。步骤0~5用来读取3个字节的内容,步骤6~7则用来产生完成信号以示一次性的报告已经读取完毕。

    116.                          /****************/ // PS2 Write Function
    117.                          
    118.                          32: // Start bit
    119.                          if( isH2L ) i <= i + 1'b1; 
    120.                          
    121.                          33,34,35,36,37,38,39,40:  // Data byte
    122.                          if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1;  end
    123.                          
    124.                          41: // Parity bit
    125.                          if( isH2L ) i <= i + 1'b1;
    126.                          
    127.                          42: // Stop bit
    128.                          if( isH2L ) i <= Go;
    129.                            
    130.                     endcase
    131.         

    以上内容为部分核心操作。步骤32~42是读取一帧数据的伪函数。

    132.         assign oTrig = isDone;
    133.         assign oData = D1;
    134.      
    135.    endmodule

    以上内容是输出驱动声明。

    ps2_demo.v

    笔者就不重复粘贴实验十一的建模图了,具体内容我们还是来看代码吧。

    1.    module ps2_demo
    2.    (
    3.         input CLOCK, RESET,
    4.         inout PS2_CLK, PS2_DAT,
    5.         output [7:0]DIG,
    6.         output [5:0]SEL,
    7.         output [2:0]LED
    8.    );
    9.        wire [1:0]EnU1;
    10.    
    11.         ps2_init_funcmod U1
    12.         (
    13.             .CLOCK( CLOCK ),
    14.              .RESET( RESET ),
    15.              .PS2_CLK( PS2_CLK ), // < top
    16.              .PS2_DAT( PS2_DAT ), // < top
    17.              .oEn( EnU1 ) // > U2
    18.         );
    19.         
    20.         wire [31:0]DataU2;
    21.         
    22.          ps2_read_funcmod U2
    23.         (
    24.             .CLOCK( CLOCK ),
    25.              .RESET( RESET ),
    26.              .PS2_CLK( PS2_CLK ), // < top
    27.              .PS2_DAT( PS2_DAT ), // < top
    28.              .iEn( EnU1 ),      // < U1
    29.              .oTrig(),
    30.              .oData( DataU2 )  // > U3
    31.         );
    32.         
    33.        smg_basemod U3
    34.        (
    35.           .CLOCK( CLOCK ),
    36.           .RESET( RESET ),
    37.            .DIG( DIG ),  // > top
    38.            .SEL( SEL ),  // > top
    39.            .iData( { 2'd0,DataU2[5],DataU2[4],DataU2[27:24],DataU2[23:16],DataU2[15:8] }) // < U2
    40.        );
    41.        
    42.        assign LED = {DataU2[1], DataU2[2], DataU2[0]};
    43.                          
    44.    endmodule

    上诉内容的连线部署基本上与图11.10差不了多少,期间第39行的 2’d0,DataU2[5],DataU2[4] 表示数码管的第一位显示 X 与 Y的符号位;DataU2[27:24] 表示数码管的第二位显示 Z的内容;DataU2[23:16] 表示数码管的第三至第四位显示 Y 的内容;DataU2[15:8] 表示数码管的第五至第六位显示 X 的内容。第42行则表示 LED[2]显示右键,LED[1]显示中键,LED[0]显示左键。

    编译完毕并且下载程序。当鼠标向西南方移动的时候,第一位数码管便会显示 4’h3,即 4’b0011,也就是说 X 与 Y 的符号位都是拉高状态(负值)。当滚轮向上滚动的时候,第二位数码管便会显示 4’hF,即4’b1111,也就是Z为负值 -1(只要滚动速度够快,负值还能更小)。至于数码管第3~4显示Y的内容(补码形式),数码管5~6则显示X的内容(补码形式)。

    细节一: 两个人,两把汤匙

    1.       else if( iEn[1] ) 
    2.           case( i )
    3.              扩展鼠标的核心操作;
    4.              伪函数;
    5.           endcase
    6.       else if(isEn[0])
    7.          case(i)
    8.               普通鼠标的核心操作;
    9.               伪函数;
    10.          endcase

    代码11.2

    PS/2 读取功能模块有一个有趣的现象,即资源多义性的问题。如代码11.2所示,PS/2读取功能模块用 if( iEn[1] ) 与 if( iEn[0] ) 表示该模块针对两种鼠标的读取操作。这种感觉好比一对兄弟在吃饭 ... 正常情况下,当然是一个人一把汤匙才对,这种比喻完全对应代码11.2的内容。

    PS/2读取功能模块负责两种鼠标的读取操作之际,里边好比有一对兄弟,一个人负责扩展鼠标的读取操作,另一个人则针对普通鼠标的读取操作。期间,伪函数就是某种操作资源,也可以看成是汤匙。为了不让两位兄弟争用一把汤匙而吵架,身为设计者的我们,应该为每个人分配一把汤匙。

    对此,我们必须多花一些钱买另一把汤匙,这样做我们可能多消耗一些逻辑资源。不过,家和为贵,为使模块可以和谐共处以致提高表达能力,要笔者多消耗一些逻辑资源,笔者也觉得值得。

    细节二:完整的个体模块

    clip_image026

    图11.13 PS/2鼠标基础模块的建模图。

    图11.13是PS/2鼠标基础模块的建模图。

    ps2mouse_basemod.v
    1.    module ps2mouse_basemod
    2.    (
    3.         input CLOCK, RESET,
    4.         inout PS2_CLK, PS2_DAT,
    5.         output oTrig,
    6.         output [31:0]oData
    7.    );
    8.        wire [1:0]EnU1;
    9.    
    10.         ps2_init_funcmod U1
    11.         (
    12.              .CLOCK( CLOCK ),
    13.              .RESET( RESET ),
    14.              .PS2_CLK( PS2_CLK ), // < top
    15.              .PS2_DAT( PS2_DAT ), // < top
    16.              .oEn( EnU1 ) // > U2
    17.         );
    18.         
    19.          ps2_read_funcmod U2
    20.         (
    21.              .CLOCK( CLOCK ),
    22.              .RESET( RESET ),
    23.              .PS2_CLK( PS2_CLK ), // < top
    24.              .PS2_DAT( PS2_DAT ), // < top
    25.              .iEn( EnU1 ),      // < U1
    26.              .oTrig( oTrig ),  // > top
    27.              .oData( oData )  // > top
    28.         );
    29.                          
    30.    endmodule
  • 相关阅读:
    CCNA 6.9
    CCNA 6.5
    Google search
    CCNA 4.14 TP Correction
    CCNA 6.3
    CCNA 6.6
    有关 英语学习的一些网站
    法语学习笔记
    垃圾邮件分类(Scala 版本)
    SQL 面试经典问题 行列互相转化
  • 原文地址:https://www.cnblogs.com/alinx/p/3962540.html
Copyright © 2020-2023  润新知