在5.1章中,笔者说过“每一件硬件资源的封装,都有自己的考虑”。
key_interface.v 考虑了“5个拥有同样功能(滤抖)按键”。
smg_interface.v 考虑了“6位数码管的动态显示”。
beep_interface.v , ps2_interface.v 考虑了“利用FIFO,来拜托对上一层模块的束缚”。
tx_interface.v, rx_interface.v 和上述蜂鸣器接口和PS2接口考虑同样的事情。
vga_interface.v , lcd_interface.v 考虑了“利用RAM来打破了显示图像信息的极限”。
以上的接口都包含两个定义:“最后建模工程”和“使独立性特质”。
当然 RTC接口的封装也离不开“封装的定义”,但是 RTC接口会比较特别。RTC接口包含了 ds1302的实时时钟芯片,如果只是“单纯的驱动”而已,那么ds1302模块已经是切切有余。
当我们封装RTC接口的时候,我们到底要“考虑什么呢?”。笔者的想法很简单,我们只要为 ds1302芯片 加入“可驱动”,“可配置”,“可输出”即可。
上图是组合模块 rtc_interface.v,该组合模块包含了“可驱动”的ds1302模块,“可显示”和“可配置”的RTC控制模块。在4.3章中(实验十三)中建模成功的 ds1302模块,我们知道它可以支持8种命令,然而我们可以基于这些事儿之上,来决定“可配置”的设计方案。
RTC控制模块扮演着“可配置”和“可显示”的同时,它也扮演着 ds1302模块的控制操作。这也是为什么笔者在实验六中一直强调 “控制模块”的重要性。
我们先来复习 ds1302模块 可以支持的8种命令:
RTC_Start_Sig[ 7..0 ] | |
位命令 | 功能 |
1000_0000 | 关闭写保护 |
0100_0000 | 变更时寄存器 |
0010_0000 | 变更分寄存器 |
0001_0000 | 变更秒寄存器 |
0000_1000 | 开启写保护 |
0000_0100 | 读取时寄存器 |
0000_0010 | 读取分寄存器 |
0000_0001 | 读取秒寄存器 |
RTC控制模块除了“可驱动”以外,还有“可输出”这个工作。在“图形”中的RTC_Sig 信号,包含了24位位宽,然而位的分配如下:
接下来就是“可配置”的方案了。Config_Sig 包含5位位宽,然而位分配如下:
Config_Sig[4..0] | |
位分配 | 意义 |
[4] | 进入配置模式|退出配置莫斯 |
[3] | +1操作 |
[2] | -1操作 |
[1] | 向左切换 ( 时 <= 分 <= 秒 ) |
[0] | 向右切换 ( 时 => 分 => 秒 ) |
Config_Sig 信号的每一个“位”都对“高脉冲敏感”,换句话说,如果某“位”接收“一个高脉冲”就有“一次性的操作”。
假设Config_Sig[4]接收一个高脉冲就“进入配置模式”。然后隔一段时间之后 Config_Sig[4] 再接收一个高脉冲就会“退出配置模式”。
(在这里笔者需要强调一点,在这里所谓的“时钟”不是物体的“时钟”,而是“时分秒”中的“时钟”。)
笔者先大致的说明一下 RTC接口的工作原理:在一开始的时候 RTC接口 初始化 DS1302 芯片,将时钟,分钟,秒钟都配置为00。然后 RTC接口,会从 00-00-00 开始计时。换句话说,在初始的状态 RTC_Sig 的输出是24'h00_00_00。
假设 Config_Sig[4] 接收一个高脉冲,DS1302 芯片就停止计时,然而 RTC_Sig 输出当前的计时状态,这时候 RTC接口就进入配置模式。在配置模式中“时钟”作为默认配置的“时间”。如果Config_Sig[3] 接收一个高脉冲,当前的“时钟值”就会递增。反之,如果 Config_Sig[2] 接收一个高脉冲,当前的“时钟值”就会递减。“时钟值”最大的值是24, 最小值是00。
假设我要配置“分钟”,Config_Sig[0] 就要接收一个高脉冲,从“配置时钟”向右切换“配置分钟”。和“配置时钟”同样的原理。如果此时Config_Sig[3] 接收一个高脉冲,当前的“分钟值”就会递增,反之Config_Sig[2] 接收一个高脉冲会使得当前的“分钟值”递减。“分钟值”最大的值是59, 最小值是00。
如果接下来我要配置“秒钟”,Config_Sig[0] 就要接收一个高脉冲。如果接下来我又要配置“时钟”,Config_Sig[1] 就要接收一个高脉冲。“秒钟值”的最大值是59,最小值是00。
在这里有一点必须注意的是:
在配置模式中,被“配置的时间”会在 RTC_Sig 输出。假设我在当前的配置模式中,我将“时钟值”配置为 23,那么 RTC_Sig 就会输出 24'h23_00_00。
“时钟”会作为“配置时间”的默认选择。换句话说,当一进入“配置模式”,立即进入“配置时钟”。
“配置时钟”作为“切换”的最左边,“配置秒钟”作为“切换”的最右边。也就是说:
时钟 <=> 分钟 <=> 秒钟。
最后,假设我最终配置的时间是 12 - 20 - 12 ,然而我已经满足时间的配置了。这时候 RTC_Sig 的输出是 24'h12_20_12。然后我要退出配置模式, 那么Config_Sig[4] 需要接收一个高脉冲, RTC接口就会从“配置模式”退出至“正常模式”。
当返回“正常模式”,RTC接口会从 12-20-12 开始计时。
3~13行是 rtc_control_module.v 的输入输出定义。
在第 18行定义了 isConfig 的标志寄存器。isConfig的默认值是逻辑0, 也就是说在初始化的状态下,RTC接口会进入“正常模式”(22行)。如果 Config_Sig[4] 接收到一个高脉冲 isConfig 的值就会介于 0~1之间切换(23~24),亦即isConfig 逻辑0代表“正常模式”,逻辑1代表“配置模式”。
27~50行是核心部分有关的寄存器声明和寄存器初始化。rData值作为 Time_Write_Data的驱动。Hour , Min, Sec 是作为“时分秒种”的暂存器。Temp 和 Comp 只作为“时间值”递增和递减操作的暂存器。Go寄存器是步骤i的返回指示。isStart是作为驱动ds1302模块的命令寄存器。所有寄存器的初始化都是清零状态。
在一开始的时候,RTC时钟写进入初始化状态:
步骤0(56~58行)关闭 DS1302芯片的写保护。步骤1(60~62行)是初始化DS1302芯片的“时钟值”。步骤2(64~66行)是初始化DS1302芯片的“分钟值”。步骤3(68~70行)是初始化DS1302芯片的“秒钟值”,同时也是启动DS1302芯片开始计数。所以说,初始化状态的“时间”是 24'h00_00_00。
步骤4~7, 是正常模式。当进入步骤4(74~76),if条件就会先判断,isConfig是否被拉高?如果isConfig不被拉高,就进入下一个步骤。步骤5是执行“读时钟”的命令,步骤6是执行“读分钟”的操作,步骤7是读秒钟的操作。最后会返回步骤4。换句话说步骤4~7是正常模式的循环。但是,一旦步骤4中if条件成立的话,就会进入“配置模式”。
步骤8是预配置, 也就是说在关闭 ds1302芯片计时的同时,初始化 Temp寄存器为Hour寄存器的值,这一点很重要,因为下一个步骤的操作需要。(在DS1302秒寄存器的最高位写入1,亦即关闭计时)。可能你会想“在为DS1302的秒寄存器写入 8'b1000_0000,的时候,会不会破坏当前秒寄存器的值呢?”。秒寄存器的值已经被暂存在 Sec。
步骤9~11是配置模式。在103行笔者是否看见这样一句代码,改代码表示将Temp的值赋予 Hour寄存器。读者尝试想象,如果在预配置至下(步骤8),没有为Temp赋予Hour的值。那么当进入配置模式,无疑103行的代码会被执行,Temp的初值为0,Hour的值已不是被破坏了?此外111,118行类似的代码到底有什么作用 ...
步骤9是“配置时钟”,在99行的if条件先判断isConfig是否为逻辑0,如果是就退出配置模式,如果不是就处于“配置时钟”的状态。103行的代码,会作为默认一直被执行着。
当 Config_Sig[3] 接收一个高脉冲,亦即“时钟值递增”的操作。Temp会暂存 Hour的值,然后 Comp寄存器陪暂存 Hour的最大值,也就是8'h23。Go寄存器指示着步骤9,然后i寄存器被赋予 4'd12 ,该表示下一个执行步骤为12。
步骤12~14是“值递增”操作。在123行if条件会先判断,如果Temp的值小于Comp的值(如果当前的时钟值小于最大的时钟值的话),Temp会递增。然后会进入步骤13。
否则的话 Temp 的值会赋予 Comp的值(当前时钟值赋予最大的时钟值),然后返回Go指示的步骤(如果当前是“配置时钟”,就会返回“配置时钟的步骤”,这也是为什么在100行,Go会指向当前的执行步骤)。
步骤13是进位操作,在127行if条件会判断Temp的个位(时钟的个位)是否大于9,
如果是就执行进位操作,然后i递增以示下一个步骤。否则i也会递增以示下一个步骤。
步骤14,在131行的if条件会判断 Temp的十位(当前“时钟值”的十位)是否大于Comp的十位(“时钟值”最大值的十位),如果是(亦即“当前时钟值”大于“时钟值最大值”)Temp就赋予Comp的值(当前“时钟值”赋予“时钟值最大值”)。然后i赋予Go的值,以示返回“配置时钟”的步骤,亦即步骤9。否则,同样i会赋予Go的值
,返回步骤9。
在步骤9时,如果Config_Sig[2] 接收一个高脉冲,Temp就会暂存Hour的值(Temp暂存当前的“时钟值”),然后Go寄存器指向当前步骤,亦即步骤9。i寄存器被赋予4'd15,也就是说下一个步骤会进入步骤15。
步骤15是递减操作。在137行if条件会先判断 Temp 的个位(当前“时钟值”的个位)大于0。如果是 Temp的个位就会递减(当前“时钟值”的个位递减1)。然后i寄存器会指向Go的值,亦即返回步骤9,返回“配置时钟”。
如果137行的if不成立,就会判断138行的if条件。Temp的个位等于0的同时,Temp的十位又大于0(当前“时钟值”的个位等于0,而且当前“时钟值”的十位大于0),就会将Temp的十位递减,Temp的个位赋予4'd9(将当前“时钟值”的十位递减1,当前“时钟值”的个位赋予9)。最后 i 寄存器会指向返回Go的值,亦即步骤9。
如果137行和138行的if条件不成立(139行),即表示Temp的值(当前的“时钟值”是8'h00)。i 寄存器会指向返回Go的值,亦即步骤9。
在 103行的这段代码很重要,因为无论是递增操作,或者是递减操作。最后操作的结果(Temp值)都要更新于Hour寄存器的值。
如果Config_Sig[0] 接收一个高脉冲(102行),亦即是向右边切换,换句话说就是从“时钟配置”向右切换到“分钟配置”。此时Temp的值必须更行为 Min的值。和103行同样的道理。当 Config_Sig[0] 就收一个高脉冲,步骤9会递增至步骤10。
在步骤10(105~111行),无疑在111行的代码会被执行,如果在102行 Temp值没有被更新为 Min 的值,不难想象得到 Min寄存器的值与 Hour寄存器的值是一样的,这显然是严重的BUG。
“分钟配置”和“时钟配置”的“递增操作”或者“递减操作”都是大同小异。 Config_Sig[3] 如果被触发,就会进入步骤12, 亦即“递增操作的步骤”。Config_Sig[2] 被触发就会进入步骤15的“递减操作”。
不同之处是: Temp 暂存的再也不是 Hour 而是 Min的值,Comp 的最大值是 8'h59 ,
Go的寄存器指向“分钟配置”的步骤。
如果 Config_Sig[0] 接收一个高脉冲,i会递增,“分钟配置”向右切换至“秒钟配置”(108行),Temp会暂存 Sec的值。如果 Config_Sig[1] 接收一个高脉冲,i会递减,“分钟配置”向左切换至“时钟配置”(109行),Temp会暂存Hour的值。
在步骤9(98~103行)没有 Config_Sig[1],在步骤11(113~118行)没有 Congfig_Sig[0]。
这也表示 “时钟配置 <=> 分钟配置 <=> 秒钟配置”,配置模式会在这3个时间配置之间切换。换句话说“时钟配置”是“向左切换的最边”,然而“秒钟配置”是“向右切换的最边”这一个事实。
在步骤12~14的递增操作和在步骤15的递增操作,在某种程度上,可以把它们看成为“递增函数和递减函数”。根据一些常用的设计方法,我们必定会建立一个“时钟递增”,“分钟递增”,“秒钟递增”,“时钟递减”,“分钟递减”和“秒钟递减”等的多个操作步骤。这无疑是会消耗许多的逻辑资源。
在某程度的根本上时钟递增”,“分钟递增”,“秒钟递增”,“时钟递减”,“分钟递减”和“秒钟递减”等步骤,都可以共用一个“递增步骤”和“递减步骤”。但是问题就在于“参数传递”和“参数返回”是有关“代码概念”的操作。我们知道Verilog HDL语言,是“硬件描述语言”,它没有“代码的概念”...
这时候我们必须把思路往后推移。笔者还记得自己在学习“微处理器”的时候(在笔者的心目中微处理器是悲剧,和单片机是不同的东西),为了是两个值相加,必须将两个值载入“操作空间”,然后使用指令是它们相加。如果使用这个思路反映到步骤12~14和步骤15的“递增递减操作”。寄存器Temp , 寄存器 Comp , 和寄存器 Go 等就有所谓的“操作空间”的意义。
在步骤9,10,11期间,如果 99,106,114行的if条件判断到 isConfig 被拉低的话。估计只有一件事情要发生,那就是“退出配置模式”。
当“退出配置模式”之后 i寄存器会赋值为 4'd1 ,亦即表示返回步骤1。
步骤1,是初始化 Hour ,但是关键点就是在 58行的 rData <= Hour。同样的步骤2的66行和步骤3的70行都是同样的意义。就是把配置后的 Hour ,Min ,Sec 作为输入数据,然后调用 DS1302模块的命令,针对DS1302芯片的时寄存器,分寄存器和秒寄存器执行更新。
最后步骤i的流程也会进入 4~7 之间,也就是说 从“配置模式的退出”,会进入步骤1执行时间值更新的操作,然后会进入“普通模式”。
在148行 RTC_Start_Sig 由 isStart 命令寄存器驱动。在149行Time_Write_Data由 rData寄存器驱动。在150行RTC_Sig由 Hour,Min,Sec寄存器驱动。
这是完成的代码,好好的浏览一番吧。
rtc_interface.v 组合模块和“图形”是一模一样。
说实话RTC接口的封装,却是有一点难度。RTC接口的“可驱动”是由DS1302模块负责但是由RTC控制模块控制。然而“可显示”和“可配置”却是由RTC控制模块负责。
对于RTC接口的配置控制,我们只要知道如何调用Config_Sig信号。Config_Sig 信号的“每一位”都有“配置的用意”。笔者不得不承认“可配置”的设计方面,确实有一点难度。但是困难归苦难,为了未来,就要克服。
读者还是把重点放在“RTC控制模块如何操作”,因为只要读者明白了“RTC控制模块如何操作”,自然而然会明白“RTC控制模块的设计思路”。
完成后的扩展图:
这一章实验主要是讲解如何为“DS1302芯片”执行封装。
第五章终于完结了。在这里笔者来个总结:
低级建模中所谓的“最后工程”是针对某个硬件“有考虑”的封装。
低级建模中所谓的“后期建模”是为后期建模的做好准备。如第二章~第四章的“基础建模”就是为“封装”做好准备,故“封装”可以称为“基础建模”的后期建模。然而“封装”又为什么做好准备?那就是下一章的主题 -“系统”建模。
说道“封装”,我们必须考虑-经过“封装”后的模块都有“独立性”的特质。我们知道Verilog HDL语言硬件描述语言,“封装”的行为会很有效的将“Verilog HDL”语言的特性带出来。因为经过“封装”以后的模块,都能“独立”的运行起来,这也好比“并行操作”的概念。
经过第五章的洗礼,读者们是不是领悟到从第一章到第五章的所有内容,任何一段信息都是在为下一章做好准备。在第二章中,笔者就提及过:“低级建模是模仿管理系统”的一种建模方法。一个大部分是由许多小部分组成。
如果换成另外一个角度去想象:
l 每一个简单的功能模块,可以看似一位员工。
l 每一个简单的控制模块,可以看似一位领导。
l 每一个简单的组合模块,可以看似一组小组。
l 每一个组合模块再组合起来,可以看似一个大组。
l 每一个大组为某种“目的”存在,而且有独立运行的能力,就成为部门(接口)。
l 最后由许多接口组合起来,就成为一个“系统”。
这就是“低级建模”最基本思路。
低级建模对于每一个模块都有区分“特质”。这好比员工是员工,领导是领导,小组是小组,大组是大组,部门是部门,各个都有自己的特征。
无论是什么样子管理系统,员工和员工之间,领导和员工之间,部门经理和领导之间,必须和谐共处。这好比是“低级建模”的“代码风格”吧。从实验一到实验二一,读者可能会发现到笔者所使用的“代码风格”都是清一色,笔者会很脸皮的告诉你,“这是低级建模的固有代码结构”。
笔者知晓,很多读者一开始的时候,都会把“步骤i”看成是某个状态机的状态。在创建“低级建模”的初头,笔者老是觉得“典型的状态机”用法实在是太魏硕了。如果是小代码量的建模,那么“典型状态机”是没有问题。但是遇见“多级建模”或者“多状态”的时候“典型的状态机”就是“见鬼”。
笔者索性就建立自己的“代码风格”,在“低级建模”里笔者使用“步骤”来取代“状态机”。果真这个决定是对的。这样的设定,给笔者在后期的实验带来许多方便。在网上有一位网友很可爱的为笔者“Verilog HDL建模技巧 - 低级建模之仿顺序操作·思路篇”评价:
“俺觉得,每一个有仿顺序操作结构的模块,都可以理解成为一个子状态机 ... ”
他的理解没有一丝错误,不过是在理解上有不同的立场。这也难怪,因为当时笔者在写这一本笔记的时候,正是“低级建模”的创建初头。除了“步骤i”具有显性的代码风格以外,还有许多许多的代码风格 ...
话说远了 ... 下一章就是大团圆的一章笔记了。在进入下一章之前,请确保读者本身已经理解,掌握“低级建模是什么”这一点重点。如果还没有很好的理解“低级建模时什么”,请务必加强对“低级建模”的理解和掌握。
学习最重要不是要得到好成绩,也不是学习速度,而是掌握学习的重点 ......
文章评论(0条评论)
登录后参与讨论