现在的位置: 首页 > 综合 > 正文

【连载】【FPGA黑金开发板】Verilog HDL那些事儿–命令式的仿顺序操作(十四)

2013年05月14日 ⁄ 综合 ⁄ 共 11971字 ⁄ 字号 评论关闭
文章目录

声明:本文为原创作品,版权归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语言是一堆乐高积木”这一回事了吧!

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

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

抱歉!评论已关闭.