zoukankan      html  css  js  c++  java
  • 【连载】【FPGA黑金开发板】Verilog HDL那些事儿命令式的仿顺序操作(十四)

    声明:本文为原创作品,版权归akuei2及黑金动力社区(http://www.heijin.org)共同所有,如需转载,请注明出处http://www.cnblogs.com/kingst/

    6

    4.3 命令式的仿顺序操作

    什么是 Verilog HDL 式的仿顺序操作!?在明白这东西之前,我们先看几个例子:

    假设我要建立 可以产生 SSS,S0S,0S0,000 这四种模块。如果模仿C语言函数会是如下:

    //基础函数

    S_Function(){...}

    O_Function(){...}

    //基于基础函数创建的函数

    SSS_Function()

    {

    S_Function(); S_Function(); S_Function();

    }

    SOS_Fucntion()

    {

    S_Function(); O_Function(); S_Function();

    }

    OSO_Fucntion()

    {

    O_Function(); S_Function(); O_Function();

    }

    OOO_Fucntion()

    {

    O_Function(); O_Function(); O_Function();

    }

    我们会很自然的,以S_Function() 和 O_Fucntion() 为基础,再建立出四个新的 SOS_Function(), SSS_Function(), OSO_Function(), 和 OOO_Fucntion()。在仿顺序语言上(如C语言),这样的方法当然,没有问题,但是在 Verilog HDL上呢?

    笔者说过“仿顺序操作”归根究底不是单纯模仿顺序操作而已,而是利用Verilog HDL语言本身的特质,去模仿顺序操作。4.1章~4.2章的实验,虽然达到以上这一点,但这只是仅限于“少个函数”而已。

    我们来看看,如果以 4.1章~4.2章为基础,来模仿以上的顺序操作的话,会是什么样一个结果。

    clip_image002

    如图上!可怕吧!逐渐建立 SSS组合模块,SOS组合模块,OSO组合模块和OOO模块。然而每一个组合模块都包含各自的 “控制模块”,“S摩斯码模块”,“O模式码模块”

    和“选择器”。最后,每一个组合模块的输出,还需要一个输出选择器来协调操作。

    我们从另一个方面来分析它的缺点:

    (一)模块的重复,资源的消耗。

    (二)建模量多,连线设计繁多。

    (三)模块调用的难度。

    诸如以上等。如果你有笔者这样的耐性,当然没有问题,但是实际上笔者也觉得非常“猥琐”,而且建模的设计也很苦难。所以我们需要另一种“仿顺序操作”的方法,毕竟4-1章 和 4-2章的方法,只适合小规模的“仿顺序操作”。

    那个方法即是“命令式的仿顺序操作”。

    何谓“命令式的仿顺序操作?”我们来看看下面的一张图,就可以知道个大概:

    clip_image004

    如上图!结果我们可以把如上的建模精简到这样的程度, 当然在各个所包含的内容中,代码的结构也是有保障的。如果继续引入以上的例子,那么:

    function_module.v

    module function_module

    (

    ......

    Function_Start_Sig,

    Function_Done_Sig,

    Pin_Out

    );

    ......

    input [1:0]Function_Start_Sig;

    input Function_Done_Sig;

    output Pin_Out;

    /******************************************/

    //定时器和延时器

    ......

    /*****************************************/

    reg [3:0]i;

    reg rPin_Out;

    reg isDone;

    ......

    always @ ( posedge or CLK or negedge RSTn )

    if( !RSTn )

    begin

    i <= 4'd0;

    rPin_Out <= 1'b0;

    isDone <= 1'b0;

    ......

    end

    ===> else if( Function_Start_Sig[1] )

    case( i )

    // S摩斯码产生

    ......

    4'd 9:

    begin isDone <= 1'b1; i <= i + 1'b1; end

    4'd10:

    begin isDone <= 1'b0; i <= 4'd0;

    endcase

    ===> else if( Function_Start_Sig[0] )

    case( i )

    // 0摩斯码产生

    ......

    4'd 9:

    begin isDone <= 1'b1; i <= i + 1'b1; end

    4'd10:

    begin isDone <= 1'b0; i <= 4'd0;

    endcase

    /*************************************************/

    assign Function_Done_Sig = isDone;

    assign Pin_Out = rPin_Out;

    /*************************************************/

    endmodule

    关于 function_module.v 关键的部分是 Function_Start_Sig 的位宽和 else if 部分。

    Function_Start_Sig 的每一位“位宽”都代表不同的“Start_Sig”。

    Function_Start_Sig[ 1..0 ]

    位命令

    功能

    10

    S莫斯码产生

    01

    O摩斯码产生

    然而 “Done_Sig” 和以往一样,没有任何变化。假设我要产生 S模式码,那么我只要往 Function_Start_Sig 输入 2'b10 即可。

    cmd_control_module.v

    module cmd_control_module

    (

    ......

    Command_Start_Sig,

    Commnad_Done_Sig,

    Function_Start_Sig,

    Fucntion_Done_Sig

    );

    input [3:0]Command_Start_Sig;

    output Command_Done_Sig;

    output [1:0]Function_Start_Sig;

    input Function_Done_Sig;

    /********************************************/

    reg [3:0]i;

    reg [1:0]isStart;

    reg isDone;

    always @ ( posedge CLK or negedge RSTn )

    if( !RSTn )

    begin

    i <= 4'd0;

    isStart <= 2'b00;

    isDone <= 1'b0;

    end

    ==> else if( Start_Sig[3] ) // 产生SSS

    case( i )

    4'd0, 4'd1, 4'd2 :

    if( Fucntion_Done_Sig ) begin isStart <= 2'b00; i <= i + 1'b1; end

    else isStart <= 2'b10;

    4'd3:

    begin isDone <= 1'b1; i <= i + 1'b1; end

    4'd4:

    begin isDone <= 1'b0; i <= i + 1'b1; end

    endcase

    ==> else if( Start_Sig[2] ) // 产生SOS

    case( i )

    4'd0, 4'd2 :

    if( Fucntion_Done_Sig ) begin isStart <= 2'b00; i <= i + 1'b1; end

    else isStart <= 2'b10;

    4'd1 :

    if( Fucntion_Done_Sig ) begin isStart <= 2'b00; i <= i + 1'b1; end

    else isStart <= 2'b01;

    4'd3:

    begin isDone <= 1'b1; i <= i + 1'b1; end

    4'd4:

    begin isDone <= 1'b0; i <= i + 1'b1; end

    endcase

    ==> else if( Start_Sig[1] ) // 产生OSO

    case( i )

    4'd0, 4'd2 :

    if( Fucntion_Done_Sig ) begin isStart <= 2'b00; i <= i + 1'b1; end

    else isStart <= 2'b01;

    4'd1 :

    if( Fucntion_Done_Sig ) begin isStart <= 2'b00; i <= i + 1'b1; end

    else isStart <= 2'b10;

    4'd3:

    begin isDone <= 1'b1; i <= i + 1'b1; end

    4'd4:

    begin isDone <= 1'b0; i <= i + 1'b1; end

    endcase

    ==> else if( Start_Sig[1] ) // 产生OOO

    case( i )

    4'd0, 4'd1, 4'd2 :

    if( Fucntion_Done_Sig ) begin isStart <= 2'b00; i <= i + 1'b1; end

    else isStart <= 2'b01;

    4'd3:

    begin isDone <= 1'b1; i <= i + 1'b1; end

    4'd4:

    begin isDone <= 1'b0; i <= i + 1'b1; end

    endcase

    /**********************************************************/

    assign Function_Start_Sig = isStart;

    assign Command_Done_Sig = isDone;

    /**********************************************************/

    endmodule

    和 function_module.v 一样 command_control_module 最关键的部分同样是 Command_Start_Sig 的“位宽”和 else if 部分,位宽分配如下:

    Command_Start_Sig[ 3..0 ]

    位命令

    功能

    1000

    产生SSS

    0100

    产生SOS

    0010

    产生OSO

    0001

    产生OOO

    假设我输入 Command_Start_Sig 是 0100,

    一、对 function_module.v 输入 2'b10 , 产生 S摩斯码 ,返回 Function_Done_Sig。

    二、对 function_module.v 输入 2'b01 , 产生 O摩斯码 ,返回 Function_Done_Sig。

    三、对 function_module.v 输入 2'b10 , 产生 S摩斯码 ,返回 Function_Done_Sig。

    四、返回Command_Done_Sig 。

    “命令式仿顺序操作”的基本思路就那么简单,下一章我们以一个实验来说明。

    实验十三:DS1302 实时时钟驱动

    虽说有关 DS1302 的资料都是网络满天飞,在这里我还是介绍一点点吧。实时时钟芯片,大家应该明白是什么吧,就是一种控制时钟的芯片。一旦初始化后,它就会随着现实的时钟一只计数。要明白DS1302芯片最主要的关键,就是“时序”和芯片本身的“寄存器”啦。

    clip_image006

    嗯!上图是DS1302芯片写操作的时序图。第一个字节是“访问寄存器的地址”,第二字节是“写数据”。在写操作的时候,都是“上升沿有效”,然而还有一个条件,就是CE(/RST)信号必须拉高。(数据都是从LSB开始发送,亦即最低位开始最高位结束)

    clip_image008

    上图是DS1302芯片读操作的时序图。基本上和写操作的时序图大同小异,区别的地方就是在第二个字节时“读数据”的动作。同样,在第二字节读数据的开始时,SCLK信号都是“下降沿有效”。嗯,别忘了CE(/RST)信号同样是必须拉高。(第一节数据是从LSB开始输出,第二节数据是从LSB开始读入)

    clip_image010

    无论是读操作还是写操作,在时序图中,第一个字节都是“访问寄存器的地址”,然而这一字节数据有自己的格式。

    BIT 7 固定。

    BIT 6 表示是访问寄存器本身,还是访问RAM空间。

    BIT 5 .. 1 表示是寄存器|RAM空间的地址。

    BIT 0 表示是访问寄存器本身是写操作,还是读操作。

    clip_image012

    上图是寄存器地址的全家福。啊有一点,我必须强调,Verilog HDL语言有的是很强的位操作,“访问寄存器的地址”可以这样表示:

    { 2'b10 , 5'd Addr, 1'b RD/W }

    (这样就可以再度提高解读性)我们知道BIT 7是固定的位,然而BIT 6表示“访问RAM空间还是访问寄存器”。在寄存器地址的全家福中,BIT 6 都是清一色的为“逻辑0”。

    假设要写秒寄存器,那么我可以如此输入:

    { 2'b10, 5'd0, 1'b0 }

    再假设我要读秒寄存器,那么我可以这样表示:

    { 2'b10, 5'd0, 1'b1 }

    clip_image014

    上图表达了每一个寄存器的字节配置。秒寄存器(第一个),前高四位(BIT7除外),表示“秒的十位”,低四位表示“秒的个位”。其他的寄存器的字节配置也是如此。但是有3个寄存器比较特别,那就是“秒寄存器”,“时寄存器”,“控制寄存器”(最后第二个)。

    秒寄存器的最高位(BIT7),如果写入“逻辑0”DS1302芯片就开始启动,反之就关闭。

    时寄存器的最高位(BIT7),表示了“逻辑1是 12小时进制”,“逻辑0 24小时进制”,笔者保守的认为,还是24小时进制比较方便工作。

    控制寄存器的最高位(BIT7),如果写入“逻辑0”表示关闭写保护,写入“逻辑1”表示打开写保护。所以呀,每当要变更寄存器的内容之前,就要关闭写保护。

    clip_image016

    上图是RAM的全家福。RAM的空间有 2^5 - 2 = 0~30 , 亦即 31 words x 8 bits 的空间。由于是访问RAM,所以“访问寄存器的地址”的BIT6必须是逻辑1。RAM地址的范围如下:

    { 2'b11 , 5'd0 , 1'b RD/W } ~ { 2'b11 , 5'd30 , 1'b RD/W }

    如果我要关闭写保护,那么我需要的操作如下:

    { 2'b10, 5'd 7, 1'b0 }

    { 8'h00 }

    如果我要在时寄存器写入 10 十进制( 如果以24小时进制 ),那么我需要如下的操作:

    { 2'b10, 5'd2, 1'b0 }

    { 4'h1, 4'h0 }

    如果我要在分寄存器写入 20 十进制,那么我需要的操作如下:

    { 2'b10, 5'd1, 1'b0 }

    { 4'h2, 4'h0 }

    如果我要在秒寄存器写入 33 十进制,那么我需要的操作如下:

    { 2'b10, 5'd0, 1'b0 }

    { 4'h3, 4'h3 }

    (在这里我们知道,秒寄存器的最高位,控制着DS1302芯片的启动和关闭,所以秒钟寄存器的配置都是留在最后才操作。因为变更秒寄存器如同启动DS1302芯片)

    如果我要在地址20, RAM空间写入 0xff的数据,那么我需要如下的操作:

    { 2'b11, 5'd 20, 1'b0 }

    { 8'hff }

    如果我要在时寄存器读出的“十位和个位”( 如果以24小时进制 ),那么我需要如下的操作:

    { 2'b10, 5'd2, 1'b1 }

    { 4'h读出时十位, 4'h读出时个位 }

    如果我要在秒寄存器读出“十位和个位”,那么我需要的操作如下:

    { 2'b10, 5'd1, 1'b1 }

    { 4'h读出秒十位, 4'h读出秒个位}

    如果我要在地址20, RAM空间读出数据,那么我需要如下的操作:

    { 2'b11, 5'd 20, 1'b1}

    { 8'h读出数据 }

    clip_image018

    我们要建立的组合模块 ds1302_module.v 基本上如上图。首先我们先把焦点放在 function_module.v 。我们知道如果以“命令式仿顺序操作”,函数模块,必须包含“两个最基本的函数”,亦即“写字节函数”和“读字节函数”。

    在上图我们可见 函数模块 的开始信号 Access_Start_Sig 的位宽有两位,它们分别是:

    Access_Start_Sig [ 1..0 ]

    位命令

    功能

    10

    写字节操作

    01

    读字节操作

    此外,还有由上层模块输入的 Words_Addr 和 Write_Data ,亦即“写一字节操作”所要求的“第一字节”和“第二字节”数据。Read_Data 和 Access_Done_Sig 分别是返回的“读出数据”和“完成信号”。

    具体的操作,我们还是直接看代码吧!

    function_module.v

    clip_image020

    clip_image021

    clip_image022

    第1~23行表示了该模块输入输出口,注意 SIO 是IO口(11行)。说道IO,

    clip_image024

    左图是一个IO的硬件设计。如果要该IO输出,这时候 isOut必须拉高,同时间 Data_Out 的数据就会输出。如果要该IO为输入,这时候我需要拉低 isOut,然而三态门会输出高阻态将“输出”载止 , 从IO口输入的数据就会经向Data_In。如果使用 Verilog HDL来表示:

    assign IO = isOut ? Data_Out : 1'bz;

    assign Data_In = IO;

    在142行,定义了 SDA 这个 IO口,是由 isOut 这个寄存器控制着“输入输出”。

    当 isOut为逻辑1时,该IO口是输出状态,反之是输入状态。然而输入只直接在操作中调用(122行)。当然我们也可以这样:

    wire SIO_In;

    assign SIO_In = SIO;

    assign SIO = isOut ? rSIO : 1'bz;

    然后在调用的地方,可以这样写, 结果也是一样。

    rData[ ( i >> 1 ) - 9 ] = SDA_In;

    clip_image025

    在45~51行定义了相关的寄存器,i是指示着执行步骤,rData用来暂存数据,rSCLK用来驱动 SCLK ,rRST用来驱动 RST , rSIO 用来驱动 SIO的输出,isOut用来控制 IO口的方向,最后的 isDone 是完成标志,亦即用来反馈完成信息。

    clip_image026

    clip_image027

    在65~98行也是Start_Sig 为 2'b10 的时候,亦即是“写字节操作”。在这段内容之中,代码完全是按照时序图执行。

    在步骤0的时候,对rData,rSCLK,rRST(CE),isOut等寄存器进行初始化,这一点很重要(68行)。

    然后在步骤1~16之中,将“第一个字节数据”,亦即“访问寄存器地址字节”发送出去。传输规则和SPI有点相似,都是时间下降沿设置数据,时间上升沿锁存数据。(70~76行)此外在步骤17再一次对rData设置为“第二个字节数据”。(79行)

    然后重复如同步骤 1~16那样(81~87行),将“第二字节数据”发送出去。在步骤34,对rRST (CE) 拉低,以示“写字节操作”已经结束。最后在步骤35~36反馈完成信号。

    clip_image028

    clip_image029

    在99~133行也是Start_Sig 为 2'b01 的时候,亦即是“读字节操作”。在读操作中,第一字节和第二字节数据显然,对时间沿的敏感不同。

    在步骤0的时候,对rData,rSCLK,rRST(CE),isOut等寄存器进行初始化,这一点很重要(103行)。

    然后在步骤1~16之中,将“第一个字节数据”,亦即“访问寄存器地址字节”发送出去。这时的数据锁存发生在时间的上升沿。(105~111行)在步骤17对IO口的方向,改变为输入,亦即将isOut设置为逻辑0。(114行)

    在步骤18~33之间是“读取一个字节数据的操作”,该动作时时间的下降沿,对SIO信后读取数据(116~122行)。在这里我再强调一下,DS1302芯片,数据的传输都是从LSB开始到MSB结束。

    最后在步骤35对rRST的拉低,以示“读字节数据”操作已经结束。然后恢复IO口为输出,亦即拉高isOut寄存器(125行),然后产生一个完成信号(127~131行)。

    clip_image030

    在122行,从DS1302芯片读取的数据会暂存在rData这个寄存器,然后该寄存器会驱动Read_Data 这个信号线(137行)。

    clip_image018[1]

    接下来我们要探讨的就是 cmd_control_module.v, 从“图形”看来 cmd_control_module.v 是 function_module.v 的上层模块。从顺序操作上看来, cmd_control_module.v的功能如下(以下只是伪代码,希望读者不要太认真,为了给读者一个感知的认识):

    Function_Module( Command, Addr, Data )

    {

    case( Command )

    {

    2'b10 : Write_Function( Addr, Data ); // Write operation

    2'b11 : Read_Fucntion( Addr ) { return Data; } // Read operation

    }

    }

    CMD_Control_Module( Command )

    {

    case( Command )

    {

    8'b10000000 : Function_module( 2'b10, {2'b10,5'd0,1'b0}, 8'h00 ); // Unprotect

    8'b01000000 : Function_module( 2'b10, {2'b10,5'd2,1'b0}, 8'h00 ); // Write hour

    8'b00100000 : Function_module( 2'b10, {2'b10,5'd1,1'b0}, 8'h00 ); // Write minit

    8'b00010000 : Function_module( 2'b10, {2'b10,5'd0,1'b0}, 8'h00 ); // Write second

    8'b00001000 : Function_module( 2'b10, {2'b10,5'd0,1'b0}, 8'h80 ); // Protect

    8'b00000100 : Function_module( 2'b01, {2'b10,5'd2,1'b1} ); // Read hour

    8'b00000010 : Function_module( 2'b01, {2'b10,5'd1,1'b1} ); // Read minit

    8'b00000001 : Function_module( 2'b01, {2'b10,5'd0,1'b1} ); // Read second

    }

    }

    从上面的伪代码看来 CMD_Control_Module 反应出 cmd_control_module 是利用 Start_Sig 的8位位宽来定义8中不同的操作。而且在这8个不同的操作之中,都对function_module.v 都有不同的操作。

    (位宽对命令分配如下)

    Start_Sig[ 7..0 ]

    位命令

    功能

    1000_0000

    关闭写保护

    0100_0000

    变更时寄存器

    0010_0000

    变更分寄存器

    0001_0000

    变更秒寄存器

    0000_1000

    开启写保护

    0000_0100

    读取时寄存器

    0000_0010

    读取分寄存器

    0000_0001

    读取秒寄存器

    cmd_control_module.v

    clip_image032

    clip_image034

    第2~34行的接口定义和“图形”是一致的。在38~39行定义了针对Words_Addr, 和 Write_Data 的rAddr 和 rData 暂存的寄存器。换句话说,rAddr寄存器是用来驱动 Words_Addr信号,rData寄存器是用来驱动 Write_Data信号。

    我们知道在8位 Start_Sig 的位宽之中,Start_Sig[7..3] 是写操作,反之 Start_Sig[2..0]是读操作。在DS1302芯片的时序中“写操作”的第一个字节需要“访问寄存器的地址”,第二个字节是“写数据”。

    然而在48~74行之中,针对这一内容对于rAddr 和 rData寄存器执行赋值。如在8'b1000_0000 的时候,是“关闭写保护”的操作,换句话说就是要往“控制寄存器”写入“数据8'h00”。故对 rAddr和 rData 赋值 {2'b10, 5'd7, 1'b0},8'h00。再举一个例子,当Start_Sig 等价于 8'b0100_0000的时候,即表示对“时寄存器”,“写入数据”。这时候对 rAddr 赋予早已经预定好的值,亦即 { 2'b10, 5'd2, 1'b0 }。然而不同的是,rData被赋予的值,是从上层发来的 Time_Write_Data。

    至于 Start_Sig[2..0] 是表示“读操作”,在DS1302芯片的时序中,读操作只需要写入“第一字节数据”,第二字节数据是从DS1302读来的。举个例子,如当 Start_Sig 等价于 8’b0000_0001 是表示从“秒寄存器读出数据”,所以关于这个操作rAddr被赋予{ 2'b10, 5'd0, 1'b1 }。

    在76~120行,是该模块的具体操作。i寄存器表示执行步骤,rRead寄存器是读出数据的暂存寄存器,isStart寄存器是用于驱动 Access_Start_Sig ,亦即是对 function_module.v 的控制寄存器(78~80行)。

    在这里我再重申一下 Start_Sig[7..3] 是“写操作”,Start_Sig[2..0]是“读操作”。为了避免跟多无谓的资源浪费,在91~118行采用了复用的写法。

    假设一个请款,当Start_Sig 等价于 8'b1000_0000 的时候,我们知道这是“关闭写保护”的操作。在同一时间rAddr和rData都会被赋值(51行)。然后91行的if条件就会成立。那么一次性行为的写操作就会发生(94~102行),当一次性的写操作完成后,它会反馈完成信号。

    我们再假设一个情况,当Start_Sig 等价于 8'b0000_0001的时候,这表示“从秒寄存器读取数据”。在同一个瞬间 rAddr 会被赋予相关的值(72行),然后在 105行 if条件就会成立,在108~116行就会完成一次的“读字节数据”的操作。

    当完成一次性的“读字节数据”,读取到的数据就会被暂存在 rRead寄存器(109行),最后反馈一个完成信号。以示上一层模块“一次性的读数据操作”已经完成。

    嗯!终于完成对 function_module.v 和 cmd_control_module.v 的解释了。最后的工作就是把它们组合成为 ds1302_module.v。

    ds1302_module.v

    clip_image036

    clip_image037

    组合模块ds1302_module.v 基本和“图形”没有什么大不同,自己看着办吧。

    实验十三说明:

    在实验十三中 function_module.v 包含了底层的操作,如果从顺序操作的角度看来 function_module.v 包含了底层函数。然而 cmd_control_module.v 不是以“函数的形式”,基于底层函数去创建更高层的函数,相反的 cmd_control_module.v 是以“位命令的形式”去“控制着每一个更高层函数的执行步骤和操作”。

    完成后的扩展图:

    clip_image039

    实验十三结论:

    这个实验和以往的实验很不同,主要是混合了Verilog HDL语言本身的位操作和仿顺序操作。实验十三重点不是在于 DS1302芯片的驱动,而是“命令式仿顺序操作”的设计。

    笔者为了使笔记精简仅仅为 cmd_control_module.v 配置8个命令而已,实际上可以达到更多,这要读者自己看着办。

    实验十三演示:

    clip_image041

    这个演示主要是演示对 ds1302_module.v 的调用。控制模块具体操作如下

    一、关闭写保护,亦即发送命令 8'b1000_0000;

    二、变更时寄存器,亦即发送命令 8'b0100_0000;

    三、变更分寄存器,亦即发送命令 8'b0010_0000;

    四、变更秒寄存器,亦即发送命令 8'b0001_0000;

    五、最后永远读取秒寄存器的值,亦即发送命令 8'b0000_0001。然后将秒个位往

    四位LED资源发送。

    exp13_demo.v

    clip_image043

    clip_image044

    在步骤0(35~37行),该模块向 ds1302_module.v 发送关闭写保护的命令,已经8'b1000_000。然后步骤1~3分别对,时寄存器,分寄存器和秒寄存器写入数据,分别是写入12时,22分,22秒(39~49行)。在步骤4,会一直从秒寄存器读取,实时时钟的秒值,换句话说该模块会一直对 ds1302_module.v 发送 8'b0000_0001的命令。每当完成“一次读字节操作”,就会对rLED赋予“秒个位”的值。最后由rLED寄存器驱动LED信号。

    实验十三演示说明:

    笔者开始懒惰了,所以只是读取“秒个位”往LED输出。啊哈哈!

    实验十三演示结论:

    ds1302_module.v 的调用演示。

    总结:

    当你把笔记看到这里,笔者只能说恭喜你了。因为经第二章到第四章,读者已经了解都是“低级建模”所叙述的概念。笔记第二章的内容是表示“低级建模”的结构和Verilog HDL语言特性的概念。笔记的第三章是表示“低级建模”的基础建模实例。然而笔记的第四章是说“仿顺序操作”这一概念。

    无论是笔记的那一章,都是“低级建模”重要的一部分,谁也不可缺少谁。可能读者会在第二章了解到 Verilog HDL 语言容易被忽略的小细节。读者又有可从第三章了解到“低级建模”一直强调的“准则”和“模块性质”的重要性等细节。笔记第四章中的“仿顺序操作”作为“低级建模”的一部分,是不可或缺的。

    因为“仿顺序操作”作为“步骤概念”是 Verilog HDL语言不可缺少的一个部分,就像笔者在第二章中的说所,Verilog HDL是一个拥有并行性质的语言,多少对于“顺序操作”都会有点不足。笔者所强调的“仿顺序操作”,就是有效的“利用Verilog HDL本身的特质”去“模仿顺序操作”,在同一时间将设计的效果推向更高,既有可能超出“顺序操作”本身的极限。

    估计当读者完成13个试验后,读者基本上已经掌握笔者所说的“低级建模”的Verilog HDL的建模技巧了。但是有一点笔者必须强调的说:“低级建模的精彩,不仅为前期建模的带来结构性的优势,而且还为后期的建模带来许多方便。”,换句话说“低级建模”真正精彩的部分是在后期的建模。

    在第一章中,笔者不是说了吗,在笔者的眼中Verilog HDL语言 和 FPGA 是一堆乐高积木,不知道多少读到这里的笔者能了解其中的含义?笔者还说过,对于 Verilog HDL 和 FPGA 语言的学习,必须有形状!?然而几乎从第一个实验到第十三个实验,笔者完全把 FPGA 和 Verilog HDL语言当成“控制器”来使用。

    还有一点,笔者一直很在意的就是“代码风格”和“建模结构”,这就是笔者在第一章提及的“建模手段”,而对于这个所谓的“建模手段”亦即这本笔记的重点 -“低级建模”。可能读者经笔者这样一提,似乎可能明白第一章中笔者所言的“FPGA+Verilog HDL语言是一堆乐高积木”这一回事了吧!

    至于什么是“后期的建模”,这一点笔者还是停留在实验的阶段。毕竟还存在许多不成熟的地方。好了,笔者也不再说什么了!但是,别忘了“低级建模”还没有结束,只是目前告一段落!

    下一章笔记开始又是一个新的旅程!

  • 相关阅读:
    Android自定义之仿360Root大师水纹效果
    Android之TextView的Span样式源码剖析
    Android之TextView的样式类Span的使用详解
    随着ScrollView的滑动,渐渐的执行动画View
    仿微信主界面导航栏图标字体颜色的变化
    android自定义之 5.0 风格progressBar
    Android性能优化之内存篇
    Android性能优化之运算篇
    How to install Zabbix5.0 LTS version with Yum on the CentOS 7.8 system?
    How to install Zabbix4.0 LTS version with Yum on the Oracle Linux 7.3 system?
  • 原文地址:https://www.cnblogs.com/kingst/p/1834255.html
Copyright © 2011-2022 走看看