什么是 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章为基础,来模仿以上的顺序操作的话,会是什么样一个结果。
如图上!可怕吧!逐渐建立 SSS组合模块,SOS组合模块,OSO组合模块和OOO模块。然而每一个组合模块都包含各自的 “控制模块”,“S摩斯码模块”,“O模式码模块”
和“选择器”。最后,每一个组合模块的输出,还需要一个输出选择器来协调操作。
我们从另一个方面来分析它的缺点:
(一)模块的重复,资源的消耗。
(二)建模量多,连线设计繁多。
(三)模块调用的难度。
诸如以上等。如果你有笔者这样的耐性,当然没有问题,但是实际上笔者也觉得非常“猥琐”,而且建模的设计也很苦难。所以我们需要另一种“仿顺序操作”的方法,毕竟4-1章 和 4-2章的方法,只适合小规模的“仿顺序操作”。
那个方法即是“命令式的仿顺序操作”。
何谓“命令式的仿顺序操作?”我们来看看下面的一张图,就可以知道个大概:
如上图!结果我们可以把如上的建模精简到这样的程度, 当然在各个所包含的内容中,代码的结构也是有保障的。如果继续引入以上的例子,那么:
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芯片写操作的时序图。第一个字节是“访问寄存器的地址”,第二字节是“写数据”。在写操作的时候,都是“上升沿有效”,然而还有一个条件,就是CE(/RST)信号必须拉高。(数据都是从LSB开始发送,亦即最低位开始最高位结束)
上图是DS1302芯片读操作的时序图。基本上和写操作的时序图大同小异,区别的地方就是在第二个字节时“读数据”的动作。同样,在第二字节读数据的开始时,SCLK信号都是“下降沿有效”。嗯,别忘了CE(/RST)信号同样是必须拉高。(第一节数据是从LSB开始输出,第二节数据是从LSB开始读入)
无论是读操作还是写操作,在时序图中,第一个字节都是“访问寄存器的地址”,然而这一字节数据有自己的格式。
BIT 7 固定。
BIT 6 表示是访问寄存器本身,还是访问RAM空间。
BIT 5 .. 1 表示是寄存器|RAM空间的地址。
BIT 0 表示是访问寄存器本身是写操作,还是读操作。
上图是寄存器地址的全家福。啊有一点,我必须强调,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 }
上图表达了每一个寄存器的字节配置。秒寄存器(第一个),前高四位(BIT7除外),表示“秒的十位”,低四位表示“秒的个位”。其他的寄存器的字节配置也是如此。但是有3个寄存器比较特别,那就是“秒寄存器”,“时寄存器”,“控制寄存器”(最后第二个)。
秒寄存器的最高位(BIT7),如果写入“逻辑0”DS1302芯片就开始启动,反之就关闭。
时寄存器的最高位(BIT7),表示了“逻辑1是 12小时进制”,“逻辑0 24小时进制”,笔者保守的认为,还是24小时进制比较方便工作。
控制寄存器的最高位(BIT7),如果写入“逻辑0”表示关闭写保护,写入“逻辑1”表示打开写保护。所以呀,每当要变更寄存器的内容之前,就要关闭写保护。
上图是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读出数据 }
我们要建立的组合模块 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 分别是返回的“读出数据”和“完成信号”。
具体的操作,我们还是直接看代码吧!
第1~23行表示了该模块输入输出口,注意 SIO 是IO口(11行)。说道IO,
左图是一个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;
在45~51行定义了相关的寄存器,i是指示着执行步骤,rData用来暂存数据,rSCLK用来驱动 SCLK ,rRST用来驱动 RST , rSIO 用来驱动 SIO的输出,isOut用来控制 IO口的方向,最后的 isDone 是完成标志,亦即用来反馈完成信息。
在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反馈完成信号。
在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行)。
在122行,从DS1302芯片读取的数据会暂存在rData这个寄存器,然后该寄存器会驱动Read_Data 这个信号线(137行)。
接下来我们要探讨的就是 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
读取秒寄存器
第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 基本和“图形”没有什么大不同,自己看着办吧。
在实验十三中 function_module.v 包含了底层的操作,如果从顺序操作的角度看来 function_module.v 包含了底层函数。然而 cmd_control_module.v 不是以“函数的形式”,基于底层函数去创建更高层的函数,相反的 cmd_control_module.v 是以“位命令的形式”去“控制着每一个更高层函数的执行步骤和操作”。
完成后的扩展图:
这个实验和以往的实验很不同,主要是混合了Verilog HDL语言本身的位操作和仿顺序操作。实验十三重点不是在于 DS1302芯片的驱动,而是“命令式仿顺序操作”的设计。
笔者为了使笔记精简仅仅为 cmd_control_module.v 配置8个命令而已,实际上可以达到更多,这要读者自己看着办。
这个演示主要是演示对 ds1302_module.v 的调用。控制模块具体操作如下
一、关闭写保护,亦即发送命令 8'b1000_0000;
二、变更时寄存器,亦即发送命令 8'b0100_0000;
三、变更分寄存器,亦即发送命令 8'b0010_0000;
四、变更秒寄存器,亦即发送命令 8'b0001_0000;
五、最后永远读取秒寄存器的值,亦即发送命令 8'b0000_0001。然后将秒个位往
四位LED资源发送。
在步骤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语言是一堆乐高积木”这一回事了吧!
至于什么是“后期的建模”,这一点笔者还是停留在实验的阶段。毕竟还存在许多不成熟的地方。好了,笔者也不再说什么了!但是,别忘了“低级建模”还没有结束,只是目前告一段落!
下一章笔记开始又是一个新的旅程!
文章评论(0条评论)
登录后参与讨论