【连载】【FPGA黑金开发板】Verilog HDL那些事儿--蜂鸣器封装(十七)
声明:本文为原创作品,版权归akuei2及黑金动力社区(http://www.heijin.org)共同所有,如需转载,请注明出处http://www.cnblogs.com/kingst/
5.3 实验十六:蜂鸣器封装
当读者看到这章,不要笑出来,笔者连蜂鸣器也不放过,蜂鸣器也逃不过封装的命运。
在前面(5.1和5.2)的试验中,无论是独立键盘,还是数码管,它们都有自己的考虑,如:独立按键必须消抖,数码管有扫描的规则 ...
那么蜂鸣器要干什么!它这家伙那么单调,只要拉低电平,这家伙就会被驱动了。既然蜂鸣器那么单调,我们就要它不单调,那怎么不单调法呢?很简单,就是在蜂鸣器的封装中,为它加入“功能”。蜂鸣器应该加入什么功能?就加入产生S摩斯码和O模式码的功能吧。
我们利用4.3章的“命令式仿顺序操作”的方法来建立它的功能模块。(请稍微复习一下)
如上图。等等!这就不是一个模块吗!?封装 等于 建立模块!?那么问题来了“模块”和“接口”本质上到底有什么的不同呢?在这里我们需要重新为“接口”加入一个“新的定义”。
在5.1和5.2章中,我们定义了“接口”是“最后的工程”,不错“封装”却是针对某个资源的最后建模工程。但是读者有没有发现,起始在5.1 和 5.2 章还包含着一个信息?
那就是“接口都是独立性”这一个事实。
如果从另一个角度去理解“接口”,你可以把它看成是一个“部门”这样的存在。在现实中我们知道“部门和部门之间”都市独立性,而且每一个部门都是有内部的操作。那么如果要把这个观点放入“蜂鸣器的封装”里边,又应该如何实现呢?
在这里我们就需要用到“FIFO”。顾名思义 FIFO 就是“先入先读”的意思。但是在宏观来看FIFO是双向口的RAM,FIFO可分为两方,“左方是写”和“右方是读”。然后经过一些内部加工,读和写可以在同时发生。
FIFO的被需要时绝对有目的的。我们重新复习一下仿顺序操作的概念。当某一个下层被使能时(Start_Sig 等于1),上一层模块就必须等待下层模块直到完成工作,才能执行下一个操作。为了避免上述的内容,我们必须借用FIFO的力量。
我们可以尝试这样想“如果我把操作信息全部缓冲到FIFO里的话 ...”,那么上一层的模块可以将全部操作信息缓冲到 FIFO里面,就可以摆脱“反馈信息”的“束缚”。这话怎么讲呢?每当下一层模块完成工作以后,可以直接从FIFO里面读取操作信息,而不必依赖上一层模块所下达的操作指令。
如果以“图形”来表示,那么结果会是如图上。上图大致的操作如下:
(一)首先上一层模块可以往FIFO里边写入大量的操作信息。然后上一层模块可以执行其他的操作,而不必在乎“反馈信息”。
(二)当FIFO里面存在信息,控制模块会从FIFO里面读取信息。信息被过滤以后,转换为执行命令,故启动功能模块。
(三)功能模很快便开始工作,直到工作结束,并且反馈完成信息给控制模块。
(四)当控制模块接收到从功能模块反馈回来的完成信息,会再度从FIFO读取信息,重复上述一样的动作,直到FIFO里面的信息全部读取完毕。
估计笔者们都对FIFO不怎么熟悉吧(笔者也是),那么我们就稍微的来理解一下FIFO的时序和操作:
(时间上升沿有效)
上图是8个深度的FIFO。在FIFO初始化的时候 wr_en_a_0拉高,然而Wr_data_a_0 和 wr_level_0 都呈现 “32” 的信息 。但是在第二个时钟的时候, 信息1就被存入深度1。第三个时钟信息2存入深度2。直到第七个时钟,当写入信息6到深度6的时候,FIFO的状态反应出“忙”,故此拉高 wr_full_0 信号,在同一个时间 we_en_a_0 被拉低。在第九个时钟 wr_en_a0 拉高,信息8被写入深度7。由于FIFO出现饱和状态,所以拉高 wr_full_0信号。
在上述的内容中,我们发现在初始化的时候FIFO的深度0是用来预备初始化信息。然而在第九个时钟的时候,基本上 FIFO已经饱和了。因此FIFO没有理由再往里面写入信息了,多以拉高 wr_en_a0 也没有任何意义。
// wr_en 写使能, wr_data 写数据, wr_full 写饱和。
下图是FIFO读数据的时序图:
(时间上升沿有效)
在初始化的时候 rd_data_b_0 呈现初始化信息“32”。在第二个时间中,从FIFO的深度7读出信息1。在第三个时间中,从深度6读出信息2。直到第八个时间从深度1读出信息8以后,FIFO反应出“FIFO没有信息了”,所以就拉高 rd_empty_0 信号。
// rd_en 读使能, rd_data 读数据, rd_empty 读空置。
上述FIFO读写内容的大致“图形”如下:
在时序图上分析FIFO看似很轻松,实际上FIFO的调用也挺别扭的。信息会发生“被卡现象”。
上图 beep_interface.v 组合模块中:
(一)FIFO拥有16个深度。
(二)蜂鸣器控制模块的主要功能是从FIFO读入一个信息经FIFO_Read_Data, 蜂鸣器控制模块,根据从FIFO读取的信息来使能“蜂鸣器功能模块”;
在这一章的实验中“蜂鸣器功能模块”只包含两个功能:就是产生S摩斯码和O摩斯码。
所以 Command_Sig 或者 Function_Start_Sig 都是2个位宽。
Function_Start_Sig = 2'b10 | 产生S摩斯码 |
Function_Start_Sig = 2'b01 | 产生O模式吗 |
整个 beep_interface.v 说穿了就是“如何调用FIFO”而已, FIFO 的信号有 :
(一)Write_Req_Sig 等价于 write_en ;
(二)Read_Req_Sig 等价于 read_en ;
(三)Full_Sig 等价于 write_full ;
(四)Empty_SIg 等价于 read_empty ;
(五)FIFO_Write_Data 等价于 write_data ;
(六)FIFO_Read_Data 等价于 read_data ;
那么这个 beep_interface.v 具体是如何运作还是直接看代码好。
beep_function_module.v
第5行中的[1:0]Start_Sig 表示了该功能模块包含了两个功能。11~40行是1ms定时器到1秒计数器。57~74行是S摩斯码的产生,反之75~92行是O摩斯码的产生。在97行,由于在实际资源,蜂鸣器的控制只挂着PNP三极管,rPin_Out的输出必须取反。
beep_control_module.v
17~31行是“加码操作”的部分,23行会判断经FIFO_Read_Data 读来的信息,将它加码为特定的命令。如通码S“1B”加码为命令“2'b10”(25行),通码O“44”加码为命令“2'b01”(26行)。然后将转换后的命令,暂存在rCmd寄存器。但是前提必须在步骤i等于1的时候才能执行(22行),和步骤i等于5的时候清零rCmd寄存器。为什么呢?
35~70行是该控制模块的核心部分。在50行,步骤0先判断FIFO是否为空,如果FIFO不是为空 Empty_Sig 信号就被拉低。如果FIFO不为空 if 条件成立,i递增以示下一步步骤。
52~56行是读FIFO的操作,在这里我先简单的介绍一下。在前面的 FIFO 时序图中,当FIFO为读状态,如果 Read_Reg_Sig ( read_en ) 不拉高,就无法把数据读出来。FIFO读数据是根据每一个时钟的上升沿,如果 Read_Reg_Sig( read_en )拉高,那么在这一个上升沿中,数据就会被读出来。换句话说,我们可以利用 Read_Reg_Sig 来充当读取数据的“锁匙”。
1:begin isRead <= 1'b1; i <= i + 1'b1; end
2:begin isRead <= 1'b0; i <= i + 1'b1; end
如上的步骤1致2表示从FIFO读取“一个深度的数据”。
在52~56行正是如上的操作。在53行将 isRead 拉高,从FIFO读取数据。在读入FIFO数据的一瞬间,“加码操作”发生了(22行),“加码成功的数据”会暂存在rCmd寄存器里。然后在56行将 isRead 拉低,为此我们已经完成“从FIFO读取数据,然后加码成功”。
在步骤3,58行先判断 rCmd的值是否为8'h00,如果 rCmd的我值为 8'h00表示这不是我们要的命令,然后步骤i清理,返回开始。如果 rCmd的值不是 8'h00 的话,i递增以示下一步步骤。
在步骤4,64行是使能 beep_function_module.v 的工作。isStart是作为 Function_Start_Sig 的驱动(75行)。加入我从FIFO读取的信息是 8'h1B,那么它会被加码为 2'b10 ,亦即执行“S摩斯码产生”的命令。接下来的工作就和“仿顺序操作一样”,直到下一层模块反馈完成信号,isStart会被清理 (63行),i递增以示下一个步骤。
步骤5(66~67行)多出来是用来清零rCmd寄存器的(30行)。如果不清零 rCmd寄存器后果会很麻烦。最后i清零,以示返回开始。
beep_interface.v
beep_interface.v 组合模块的结果和“图形”一样。自己看着办吧。
实验十六说明:
实验十六的重点,就是“如何对FIFO读取”而已。其余的建模部分都是以往实验的复习。
完成扩展图:
实验十六结论:
在某种程度来说,实验十六的蜂鸣器接口,标准操作信息是“键盘通码”。
实验十六演示:
在上图的组合模块中,控制模块对蜂鸣器接口写入“8'h1B, 8'h44, 8'h1B”这三个信息,然后就停止操作。接着,蜂鸣器接口却会发出“SOS信号”实际上对蜂鸣器接口的调用就是“如何对FIFO写入信息”这一回事。
beep_interface_demo.v
这份源码有分为两种对FIFO写入的办法。
办法一:
在拉高 isWrite 的三个时间内,同时写入3份信息。然后再追加写入一份 8'h00以使得达到强制挤出的效果。写完后拉低isWrite,以示一次性的写入操作已经结束(29~41行)。
办法二:
办法比较傻瓜,但是解读性却很好。把 isWrite 当着写入数据的钥匙。在拉高 isWrite 的同时写入数据,然后在下一个步骤拉低 isWrite 以示一次性的写数据操作已经完成。(44~60行)。
如果给笔者选择,笔者会选择办法二。原因是办法二比较“和蔼可亲”。还有一点,就是Full_Sig 这个信号是从 beep_interface.v 反馈出有关FIFO的空间状态。如果Full_Sig 被拉高,亦即 beep_interface.v 中的 FIFO 全部 16个深度 空间已经被写满了。所以每一次要向 beep_interface.v 写入信息的时候,必须判断 Full_Sig 是否是被拉高的状态(47,53,59行)。第65行写入8'h00追加信息,以使得达到强制挤出的效果。
实验十六演示说明:
在这个演示中是“beep_interface.v 如何调用”等于“FIFO如何写入”这一回事。
为什么在 beep_interface_demo.v 中,当写入 3个信息至 FIFO以后,还将无相关的 8'h00 信息写入呢?原因很简单,8'h00信息用来挤出前3个信息中的最后一个。如果不把第三个信息挤出,它就会卡在FIFO里边。
为什么在FIFO里边的第3个信息,如果不把它挤出,就会发生被卡现象?这个笔者也说不清楚,只是实验结果如此 ......
实验十六演示结论:
在这一章中我们明白到,“接口”的定义除了“最后的工程”以外还有“接口是独立”这一个定义。实际上FIFO是用来缓冲两个时间域不同的访问,在某种程度上“数据缓冲”的作用有如“仓库”。所以我们可以利用这个“仓库”作为“接口的输入”,以致“接口”有独立性的作用。
所以呀,当上一层模块调用某个“接口”时,只要把“信息”写入“仓库”即可,上一层模块用不着与该接口“互动”而被“束缚着”。该接口如果发现“仓库”有信息,就处理信息,如果“仓库”里没有信息就作罢。
posted on 2010-10-29 18:51 FPGA黑金开发板 阅读(1603) 评论(0) 编辑 收藏 举报