实验目的
本实验将利用前面实现过的键盘和显示器功能来搭建一个简单的字符输入界面,通过该系统的实现深入理解多个模块之间的交互和接口的设计。
模块设计
基础功能实现
基础功能实现的时候,我用了两个思路。
首先基础功能有
- 支持所有小写英文字母和数字输入
- 输入至行尾后自动换行,输入回车也换行。
- 一直按压某个键时,重复输出该字符。
我和同学讨论的时候,我说第三个最不好实现,我同学都说第三个最好实现,实际上是实现的方法不一样导致的
我的方法:在实验七中,有键盘计数功能,按键按下,抬起,就算一次按键,按键数即为现在要写入显存的地址index,这就实现了单个字符的输入,在键盘控制模块稍加修改,也能实现回车功能,但是比较难以添加重复输入字符功能
我同学的方法,键盘控制模块只需要提供现在的按键,有按键就返回按键asc2码,没按键就返回0,在顶层模块中,设置一个较慢的时钟(如20Hz),检测键盘输入的按键存在了几个周期,每存在一个周期,就往显存里写一个字符,即在屏幕上多显示一个字符
两种方法的比较:我的方法能很好的完成单个按键输入,但是难以完成多个按键输入。我同学的方法可以实现所有基础功能,但是要求用户在打字时,如果不想连续输入,那么每个按键按下的时间必须是一个时钟周期。显然,这样的设计是反人类的,我们在第二阶段要对其进行改进
基础功能这个地方是核心
always @(CLOCK_50)begin
block_addr <= (vaddr >> 4) * 70 + ((haddr) / 9);
asc2_address <= (asc2 << 4) + (vaddr % 16);
if(font[haddr % 9] == 1'b1&&haddr<=629)
vgadata<=12'b111111111111;
else
vgadata<=12'b0;
end
接口
两个时钟,一个是vga模块用的,频率很高,一个是扫描键盘的输入的,频率仅为30hz
显存用的是双口双时钟的RAM,你慢慢写,我快快刷新显示屏
clkgen #(25000000) clkgen_1 (CLOCK_50,1'b0,1'b1,VGA_CLK);
clkgen #(30) clkgen_2 (CLOCK_50,1'b0,1'b1,keyboard_CLK);
ram2port ram1(
.rdaddress(block_addr),
.wraddress(writeaddress),
.rdclock(CLOCK_50),
.wrclock(keyboard_CLK),
.data(writedata),
.wren(we),
.q(asc2));
rom1port rom1(
.address(asc2_address),
.clock(CLOCK_50),
.q(font)
);
keyboard my_board(
.clk(CLOCK_50),
.HEX2(HEX2),
.HEX3(HEX3),
.data(inputdata),
.shift(shift),
.cap(cap),
.big_data(biginputdata),
.ps2_clk(PS2_CLK),
.ps2_data(PS2_DAT),
);
vga_ctrl my_vga(
.pclk(VGA_CLK),//25MHz时钟
.reset(1'b0),//reset
.vga_data(vgadata),//vgadata[11:0]
.h_addr(haddr),
.v_addr(vaddr),
.hsync(VGA_HS),//列同步信号
.vsync(VGA_VS),//行同步信号
.valid(VGA_BLANK_N),//消隐信号
.vga_r(VGA_R),
.vga_g(VGA_G),
.vga_b(VGA_B));
因为实现了几个拓展功能,所以代码都写到一起了,不太好拆分说,重要的地方拆下来说一下,有些地方ifelse写的比较冗余,懒得改了,领会精神,逻辑是肯定对的
emmm,让我看看,最核心的…,应该是下面这部分
这部分代码有拓展的影子,不过无伤大雅,比如index <= index + 70 - (index % 70)+2;,实际上那个2是行首的命令提示符
always @ (posedge keyboard_CLK)begin
if(inputdata != 0) begin//如果现在有输入
if(inputdata == 8'h0d) begin//如果输入是回车的话,index移到下一行开头
index <= index + 70 - (index % 70)+2;
last_input<=inputdata;
writedata<=8'b0;
writeaddress<=index;
we<=1;
end
else begin//如果输入是普通字符的话,index++
index <= index + 1;
last_input<=inputdata;
we<=1;
writedata<=inputdata;
writeaddress<=index;
end
end
else//如果没有写入,写使能置零,避免奇怪的事情发生
we<=0;
end
我实现的拓展功能
- 非反人类键盘
首先我解决的就是上文中提到的,如果想输入单个字符,按键按下时间必须是一个时钟周期的问题
解决的方法是,设置一个sticky,last_input,记录下相同的按键信号保持的时间,保持到一定的周期,那说明你已经按着相同的键好久了,你是想连续输入,如果没有到这个周期,都算作你按了一下
if(inputdata==0)//如果刚才松开按键了
last_input<=0;
if(inputdata != 0&&inputdata!=last_input) begin//如果输入了一个和刚才不同的按键
sticky<=0;//记录这个按键的保持时间
last_input<=inputdata;
....//各种按键的处理
end
else if(inputdata != 0&&inputdata==last_input) begin//如果输入了一个和刚才相同的按键
sticky<=sticky+1;
if(sticky>=20) begin
....//和上面一毛一样各种按键的处理
end
end
2.shift,caps大小写
简单,略
3.光标
我只实现了光标的显示,没有实现方向键控制光标
光标的显示就是在显存index位置,每15周期输入一个‘|’,每15周期输入一个‘ ’,不移动index,(因为我是30hz的键盘时钟)
4.命令提示符
按下回车的接下来两个周期在index-1,index-2输入想显示的符号即可
5.删除
这个难一些,一个是要求删除到上一行的时候,需要删除到最后一个非空字符,也就是按回车的位置,这需要用一些寄存器记录每一行最后一个非空字符的位置,我还遇到一个小bug,删除的时候光标前面的字符有时候会删除不掉,打了个小补丁,在光标前面持续写入空格即可
全部代码
代码写的很丑,但是还是贴出来吧。因为我写的时候也是很蒙,没有现在那这样思路清晰。然后写的时候遇到了很多问题,打了很多补丁,导致代码就很冗长,有很多其实是可以合并到一起的。
小结
算是我的一个笔记也是日记吧,这个实验写了很久的时间,有些地方我说的简单,实际上一个bug就要写好久,就这么说吧,最低要求,在显示器上显示已经提前设置好的字符,这个功能我就写了一上午。但是写完收获还是挺大的,感觉对于时序逻辑电路的编程理解好了不少。如果有学弟学妹写到这个实验,读到我的博客,希望能对你有一些帮助呀。