官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师

明德扬至简设计法原理与应用-2.1FPGA设计思想

发布时间:2021-08-22   作者:admin 浏览量:

第二阶段  项目实践

第一章  FPGA设计思想

本文档编号:001500000080

需要看对应的视频,请点击视频编号:001500000089

1、本节是讲解详细讲解了如何正确的看懂 信号波形

1.1 看波形图的方法

本书的D触发器一章、怎么看FPGA波形一节中,讲述下如何看信号的波形,读者只需要记住一个规则:时钟上升沿看信号,看到的是信号变化前的值。有兴趣的读者,可以返回再细看那一章内容。

111

例如图111,在第4个时钟上升沿看信号a,则是看到a的值为1(变化前);看信号c,看到c的值为0(变化前);在第5个时钟上升沿看信号b,看到b的值为1,看信号c,看到c的值为1。

112

例如图112,在第5个时钟上升沿看信号dout,其值为1;看信号cnt,其值为1,而不是2。

以上就是看波形的方法,该方法的由来,可以参考本书的D触发器一章、怎么看FPGA波形一节的内容。当然,使用该方法是有前提的:所有信号都是同步信号;波形是理想的波形。


1.2 至简设计法的四种设计类型

学习FPGA,最关键的是学什么?

笔者发现,有部分读者将学习的重点放在接口知识、算法原理等理论知识层面。例如,学习串口的时候,把学习重点放在:什么是串口、串口有什么优势、什么时候用到串口等理论。至于串口代码,也是借鉴和模仿,原代码是什么结构,自己设计的代码也要有这种结构,原代码有什么信号,自己也要有这些信号等。如果该串口功能稍加改动,则陷入完全无从下手的状态。

每个工程师都有自己的代码风格,甚至有些工程师今天的风格和昨天的风格都会不一样。网络上的代码自然也是良莠不齐,一味靠模仿,能成长为高手,那就奇怪了。

明德扬认为,学习FPGA应该是为了提高自己的设计开发能力。学习串口,不是为了懂得这个串口,而是通过这个串口例子,学习其设计思路和方法,以便应用到其他接口上。

明德扬认为,设计思路和方法,不应过多过杂,而是形成自己的一套模式。今天学少林铁头功,明天学武当太极,后天学华山剑法,先不说精力问题,能达到精通状态吗?

明德扬研发了一套通用的设计方法:至简设计法。

至简设计法从宏观上,适应所有的功能设计需求。例如,无论是什么功能,我们都先将其转化成需求波形。然后在此基础上设计模块架构;在模块架构基础上设计信号。这步骤都是通用的、是固化的。

至简设计法在微观上,制定了实用的规范。详细到什么时候添加信号;怎么添加信号;添加信号名字是什么等,我们都做了详细的规定。

大部分的FPGA设计,明德扬将其归类下面讲述的4种类型。无论多复杂的功能,都是这4种类型的变种。下面通过4个典型案例的设计,来讲述至简设计法。


1.2.1 至简设计法设计类型1

案例1:当收到en=1后,dout产生一个宽度为10个时钟周期的高电平脉冲。113是功能波形图。

图 113

根据看波形规则,在第3个时钟上沿的时候,看到en==1,根据功能要求,上升沿之后dout就会变为110个时钟周期后,即第13个时钟上升沿时,dout将变为0

推理1:从功能要求中,看到数字10,我们就知道要计数,要使用计数器。

推理210个是指dout==1的次数为10个时钟周期,所以该计数器数的是dout==1的次数,因此看到dout==1时,计数器就会加1

此外,明德扬还制定了计数器要遵守的原则

原则1:初值一定为0。复位后,计数器一定要为0

原则2:数到最后一个时,要及时清0

根据上面2个推理和原则,补充计数器信号cnt,更新后的波形如图114。

114

从功能要求和波形图,我们确认,计数器cnt是对dout==1进行计数,并且一共数10个。为此,在GVIM编辑器中输 入“Jsq”并回车,将出现图115的代码。

115

在第13行,输入dout==1,在第14行代码中,输入10-1,这样就完成了计数器设计,如 116。

116

add_cnt表示:计数器cnt1条件。

end_cnt表示:计数器数到最后一个,也称之为结束条件。

1161~11代码功能:时钟上升沿时,如果计数器加1条件有效,并且是数到最后一个,则计数器清零;如果计数器加1条件有效,但不是最后一个,则计数器就加1;其他时候,计数器就保持不变。

那么加1条件,即add_cnt是什么呢?在第13行进行了定义。该行代码表示,dout==1就是计数器的加1条件。

那么结束条件,即end_cnt是什么呢?在第14行进行了定义。该行代码表示,数到10个就结束。其中我们关注的是那个数字10,而-1是固定的格式。

add_cnt && cnt==10-1,含义是表示“数到第10个的时候”,add_cnt && cnt==x-1表示“数到第 x个的时候”。记住这个规则。end_cnt==1也表示数完了。

设计好计数器cnt后,我们就可以设计输出信号dout了。仔细分析dout,该信号有两个变化点:变1和变0。我们分 析原因,dout1是由于收到en==1dout0,则是数到了10个或者是数完了。所以综上所述,dout的代码是:

1

2

3

4

5

6

7

8

9

10

11

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

dout <= 0 ;

end

else if(en==1) begin

dout <= 1 ;

end

else if(end_cnt) begin

dout <= 0 ;

end

end

至此,我们完成了主体程序的设计,接下来补充module的其他部分。

module的名称定义为my_ex1。并且我们已经知道该模块有4个信号:clkrst_nendout。为此,代码如下:

1

2

3

4

5

6

module my_ex1(

clk      ,

rst_n    ,

en       ,

dout

);

其中clkrst_nen是输入信号,dout是输出信号,并且4个信号都是1比特的,根据这些信息,我们补充输入输出端口定义。代码如下:

1

2

3

4

input    clk     ;

input    rst_n   ;

input    en      ;

output   dout    ;

接下来定义信号类型。

cnt是用always产生的信号,因此类型为regcnt计数的最大值为9,需要用4根线表示,即位宽是4位。add_cntend_cnt都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg  [ 3:0]   cnt      ;

wire          add_cnt  ;

wire          end_cnt  ;

dout是用always方式设计的,因此类型为reg。并且其值是0或者11根线表示即可。因此代码如下:

1

reg           dout     ;

至此,整个代码的设计工作已经完成。整体代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

module my_ex1(

clk      ,

rst_n    ,

en       ,

dout

);

input    clk     ;

input    rst_n   ;

input    en      ;

output   dout    ;

reg [ 3:0]  cnt     ;

wire        add_cnt ;

wire        end_cnt ;

reg         dout    ;

always @(posedge clk or negedge rst_n) begin

if(!rst_n) begin

cnt <= 0;

end

else if(add_cnt) begin

if(end_cnt)

&nbs p; cnt <= 0;

else

&nbs p; cnt <= cnt + 1;

end

end

assign add_cnt = (dout==1);

assign end_cnt = add_cnt && cnt==10 -1 ;

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

dout <= 0;

end

else if(en==1) begin

dout <= 1;

end

else if(add_cnt && cnt==10-1)begin

dout <= 0;

end

end

endmodule

1.2.2 至简设计法设计类型2

2. 当收到en=1后,dout间隔3个时钟后,产生宽度为2个时钟周期的高电平脉冲。

117

如上面波形图所示,在第3个时钟上升沿看到en==1,间隔 3个时钟后,dout1,再过2个时钟后,dout0

根据案例1的经验,出现大于1的数字时,就需要计数。我们这里有数字23,建议的计数方式如下。

118

当然,其他计数方式最终也能实现功能。但明德扬的总结是上面方式最好,实现的代码将是最简的,其他方式则稍微复杂。

接下来判断计数器的加1条件。与案例1不同的是,计数器加1区域如下图阴影部分,但图中没有任何信号来指示此区域 。

119

为此,添加一个名字为flag_add”的信号,刚好覆盖了阴影部分,如下图。

120

补充该信号后,计数器的加1条件就变为flag_add==1,并且是数5个。代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

always @(posedge clk or negedge rst_n) begin

if (rst_n==0) begin

cnt <= 0;

end

else if(add_cnt) begin

if(end_cnt)

&nbs p; cnt <= 0;

else

&nbs p; cnt <= cnt+1 ;

end

end

assign add_cnt = flag_add==1;

assign end_cnt = add_cnt  && cnt == 5-1 ;

flag_add2个变化点,变1和变0。变1的条件是收到en==1,变0的条件是计数器数完了,因此代码如下:

1

2

3

4

5

6

7

8

9

10

11

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

flag_add <= 0;

end

else if(en==1) begin

flag_add <= 1;

end

else if(end_cnt) begin

flag_add <= 0;

end

end

dout也有2个变化点:变1和变0。变1的条件是“3个间隔之后”,也就是“数到3个的时候”;变0的条件是数完了。代码如下:

1

2

3

4

5

6

7

8

9

10

11

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

dout <= 0;

end

else if(add_cnt && cnt==3-1)begin

dout <= 1;

end

else if(end_cnt) begin

dout <= 0;

end

end

至此,我们完成了主体程序的设计,接下来是补充module的其他部分。

module的名称定义为my_ex2。并且我们已经知道该模块有4个信号:clkrst_nendout。为此,代码如下:

1

2

3

4

5

6

module my_ex2(

clk      ,

rst_n    ,

en       ,

dout

);

其中clkrst_nen是输入信号,dout是输出信号,并且4个信号都是1比特的,根据这些信息,我们补充输入输出端口定义。代码如下:

1

2

3

4

input    clk     ;

input    rst_n   ;

input    en      ;

output   dout    ;

接下来定义信号类型。

cnt是用always产生的信号,因此类型为regcnt计数的最大值为4,需要用3根线表示,即位宽是3位。add_cntend_cnt都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg   [ 2:0]   cnt     ;

wire           add_cnt ;

wire           end_cnt ;

dout是用always方式设计的,因此类型为reg。并且其值是0或者11根线表示即可。因此代码如下:

1

reg            dout    ;

flag_add是用always方式设计的,因此类型为reg。并且其值是0或者11根线表示即可。因此代码如下:

1

reg            flag_add  ;

至此,整个代码的设计工作已经完成。整体代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

module my_ex2(

clk      ,

rst_n    ,

en       ,

dout

);

input    clk     ;

input    rst_n   ;

input    en      ;

output   dout    ;

reg   [ 2:0]   cnt     ;

wire           add_cnt ;

wire           end_cnt ;

reg            flag_add  ;

reg            dout    ;

always @(posedge clk or negedge rst_n) begin

if(!rst_n) begin

cnt <= 0;

end

else if(add_cnt) begin

if(end_cnt)

&nbs p; cnt <= 0;

else

&nbs p; cnt <= cnt + 1;

end

end

assign add_cnt = flag_add==1;

assign end_cnt = add_cnt && cnt==5-1 ;

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

flag_add <= 0;

end

else if(en==1) begin

flag_add <= 1;

end

else if(end_cnt) begin

flag_add <= 0;

end

end

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

dout <= 0;

end

else if(add_cnt && cnt==3-1)begin

dout <= 1;

end

else if(end_cnt) begin

dout <= 0;

end

end

endmodule

经过这个案例,我们做一下总结:在设计计数器的时候,如果计数区域没有信号来表示时,可补充一个信号flag_add


1.2.3 至简设计法设计类型3

案例3. 当收到en1=1时,dout产生3个时钟周期的高电平脉冲;当收到en2==1时,dout产生2个周期的高电平脉冲 。下面波形图描述了该功能。

121

图中,第3个时钟上升沿收到en1==1,所以dout1并且持续3个时钟周期;在第9个时钟上升沿看到en2==1,所以 dout1并且持续2个时钟周期。注意,en1==1en2==1的出现是没有顺序的。

有读者可能会问,如果en1==1en2==1同时出现,或者说在dout==1期间,出现了en1==1或者en2==1,该怎么办?请不要考虑这种情况,本案例假设永远不会出现该情况。明德扬在模块划分规范时,会要求各个模块之间配合清楚,这有助于简化我们的设计,精简系统。

看到大于1的数字,就知道要计数。推荐的计数方式如下:

122

首先,不要用2个计数器分别计两种情况。这是因为这2个计数器都是不同时计数的,是可以合并的。同时,我们可以知道,这两种情况都是计算dout==1的次数。

在确认计数器数多少个时,我们遇到了问题。因为这个计数器有时候数到3个就清零(en1==1触发的波形),有时候数到2个就清零(en2==1触发 的波形)。此时,我们建议你用变量x代替,即数x个。注意,verilog是没有变量的概念的,这个变量,是明德扬提出的一个设计概念,x本质上还是一个信号。

引入变量有什么用呢?设计计数器时就方便了,该计数器加1条件是dout==1,数x个就结束,因此代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

always @(posedge clk or negedge rst_n) begin

if(!rst_n) begin

cnt <= 0;

end

else if(add_cnt) begin

if(end_cnt)

&nbs p; cnt <= 0;

else

&nbs p; cnt <= cnt + 1;

end

end

assign add_cnt = dout==1;

assign end_cnt = add_cnt && cnt==x-1 ;

甚至我们还可以写出dout的代码,dout1的条件是:en1==1或者en2==1;变0的条件是:计数器数完了。所以代码如下:

1

2

3

4

5

6

7

8

9

10

11

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

dout <= 0;

end

else if(en1==1 || en2==1)begin

dout <= 1;

end

else if(end_cnt) begin

dout <= 0;

end

end

我们再设计一下变量x,我们知道计数器en1==1触发的时候数3个就清零,en2==1触发的时候数到2个就清零,为此增加一个信号flag_sel来区分这两种情况,flag_sel==0表示是en1==1触发的,flag_sel==1表示是en2==1触发的,波形如下:

123

flag_sel0的条件是遇到en1==1flag_sel1的条件是遇到en2==1,为此flag_sel的代码如下。

1

2

3

4

5

6

7

8

9

10

11

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

flag_sel <= 0;

end

else if(en1==1) begin

flag_sel <= 0;

end

else if(en2==1) begin

flag_sel <= 1;

end

end

有了flag_sel,我们就好区分x的值了。 flag_sel0时,x3(数3个清零);flag_sel1时,x2(数2个清零),此时要用组合逻辑设计x,不然会出错的。代码如下:

1

2

3

4

5

6

always  @(*)begin

if(flag_sel==0)

x = 3;

else

x = 2;

end

至此,本工程的主体程序已经设计完毕,本题,我们使用了变量x,这是明德扬的至简设计方法中的变量法。

主体程序完成后,我们补充模块的其他部分。

module的名称定义为my_ex3。并且我们已经知道该模块有5个信号:clkrst_nen1en2dout。为此 ,代码如下:

1

2

3

4

5

6

7

module my_ex3(

clk      ,

rst_n    ,

en1      ,

en2      ,

dout

);

其中clkrst_nen1en2是输入信号,dout是输出信号,并且5个信号都是1比特的,根据这些信息,我们补充输入输出端口定义。代码如下:

1

2

3

4

5

input    clk     ;

input    rst_n   ;

input    en1     ;

input    en2     ;

output   dout    ;

接下来定义信号类型。

cnt是用always产生的信号,因此类型为regcnt计数的最大值为2,需要用2根线表示,即位宽是2位。add_cntend_cnt都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg  [ 1:0]   cnt      ;

wire          add_cnt  ;

wire          end_cnt  ;

dout是用always方式设计的,因此类型为reg。并且其值是0或者11根线表示即可。因此代码如下:

1

reg           dout     ;

flag_sel是用always方式设计的,因此类型为reg。并且其值是0或者11根线表示即可。因此代码如下:

1

reg           flag_sel ;

x是用always方式设计的,因此类型为reg,并且其值最大为3,用2根线表示即可。因此代码如下:

1

reg [ 1:0]    x        ;

至此,整个代码的设计工作已经完成。整体代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

module my_ex3(

clk      ,

rst_n    ,

en1      ,

en2      ,

dout

);

input    clk     ;

input    rst_n   ;

input    en1     ;

input    en2     ;

output   dout    ;

reg  [ 1:0]   cnt      ;

wire          add_cnt  ;

wire          end_cnt  ;

reg           dout     ;

reg           flag_sel ;

reg [ 1:0]    x        ;

always @(posedge clk or negedge rst_n) begin

if(!rst_n) begin

cnt <= 0;

end

else if(add_cnt) begin

if(end_cnt)

&nbs p; cnt <= 0;

else

&nbs p; cnt <= cnt + 1;

end

end

assign add_cnt = dout==1;

assign end_cnt = add_cnt && cnt==x-1 ;

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

dout <= 0;

end

else if(en1==1 || en2==1)begin

dout <= 1;

end

else if(end_cnt) begin

dout <= 0;

end

end

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

flag_sel <= 0;

end

else if(en2==1) begin

flag_sel <= 1;

end

else if(en1==1) begin

flag_sel <= 0;

end

end

always  @(*)begin

if(flag_sel==0)

x = 3;

else

x = 2;

end

endmodule

总结:设计时,我们不要受具体数字的影响,而是仔细识别信号的一致性动作,然后利用变量法来设计。这样就能设计出精妙的代码。


1.2.4 至简设计法设计类型4

案例4. 当收到en=1时,dout间隔1个时钟后,产生2个时钟周期的高电平脉冲,并且重复3次。

124

上面波形图显示了描述的功能。第3个时钟上升沿收到en==1,所以dout间隔1个时钟后变1并且持续2个时钟周期,这个动作重复3次,结束。

看到大于1的数字,就知道要计数。下面的计数方式非常普遍:

125

即用一个计数器,从头数到尾。这个计数器的设计很简单,但产生dout信号就不容易了。

明德扬推荐的计数方式如下:

126

利用2个计数器。cnt0就如案例2一样,数的是间隔和高电平时钟;而计数器cnt1数的是重复次数。

如案例2相同,需要添加信号flag_add来指示cnt0的加1区域,波形如下图。

127

所以cnt0的加1条件是flag_add==1,计数3个就清零。

仔细观察cnt1可以看到,每次cnt0数完后,cnt1就会加1。所以cnt1的加1条件是end_cnt0,计数3个就清零。从而我们可以设计出cnt0cnt1的代码,输入Jsq2,即可调出模板。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

always @(posedge clk or negedge rst_n) begin

if(!rst_n) begin

cnt0 <= 0;

end

else if(add_cnt0) begin

if(end_cnt0)

&nbs p; cnt0 <= 0;

else

&nbs p; cnt0 <= cnt0 + 1;

end

end

assign add_cnt0 = flag_add==1;

assign end_cnt0 = add_cnt0 && cnt0==3-1 ;

always @(posedge clk or negedge rst_n)begin

if(!rst_n) begin

cnt1 <= 0;

end

else if(add_cnt1) begin

if(end_cnt1)

&nbs p; cnt1 <= 0;

else

&nbs p; cnt1 <= cnt1 + 1;

end

end

assign add_cnt1 = end_cnt0;

assign end_cnt1 = add_cnt1 && cnt1==3-1 ;

flag_add有两个变化点:变1和变0。变1是因为en==1,变0是因为重复次数都完了,也就是end_cnt1。所以flag_add代码如下。

1

2

3

4

5

6

7

8

9

10

11

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

flag_add <= 0;

end

else if(en==1) begin

flag_add <= 0;

end

else if(end_cnt1) begin

flag_add <= 1;

end

end

dout有两个变化点:变1和变0。在cnt0数到1时(一个间隔)时变1,在cnt0数完时变0,所以dout的代码如下。

1

2

3

4

5

6

7

8

9

10

11

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

dout <= 0;

end

else if(add_cnt0 && cnt0==1-1)begin

dout <= 1;

end

else if(end_cnt0) begin

dout <= 0;

end

end

至此,本工程的主体程序已经设计完毕,之后需要读者补充信号定义、输入输出定义了。

module的名称定义为my_ex3。并且我们已经知道该模块有5个信号:clkrst_nendout。为此,代码如下:

1

2

3

4

5

6

module my_ex4(

clk      ,

rst_ n    ,

en       ,

dout

);

其中clkrst_nen是输入信号,dout是输出信号,并且4个信号都是1比特的,根据这些信息,我们补充输入输出端口定义。代码如下:

1

2

3

4

input    clk     ;

input    rst_n   ;

input    en      ;

output   dout    ;

接下来定义信号类型。

cnt0是用always产生的信号,因此类型为regcnt0计数的最大值为2,需要用2根线表示,即位宽是 2位。add_cnt0end_cnt0都是用assign方式设计的,因此类型为wire。并且其值是 0或者11个线表示即可。因此代码如下:

1

2

3

reg   [ 1:0]    cnt0     ;

wire            add_cnt0 ;

wire            end_cnt0 ;

cnt1是用always产生的信号,因此类型为regcnt1计数的 最大值为2,需要用2根线表示,即位宽是2位。 add_cnt1end_cnt1都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg   [ 1:0]    cnt1     ;

wire            add_cnt1 ;

wire            end_cnt1 ;

dout是用always方式设计的,因此类型为reg。并且其值是0或者11根线表示即可。因此代码如下:

1

reg        dout    ;

flag_add是用always方式设计的,因此类型为reg。并且其值是0或者11根线表示即可。因此代码如下:

1

reg           flag_add  ;

至此,整个代码的设计工作已经完成。整体代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

8

module my_ex4(

clk      ,

rst_n    ,

en       ,

dout

);

input    clk     ;

input    rst_n   ;

input    en      ;

output   dout    ;

reg   [ 1:0]    cnt0     ;

wire            add_cnt0 ;

wire            end_cnt0 ;

reg   [ 1:0]    cnt1     ;

wire            add_cnt1 ;

wire            end_cnt1 ;

always @(posedge clk or negedge rst_n) begin

if(!rst_n) begin

cnt0 <= 0;

end

else if(add_cnt0) begin

if(end_cnt0)

&nbs p; cnt0 <= 0;

else

&nbs p; cnt0 <= cnt0 + 1;

end

end

assign add_cnt0 = flag_add==1;

assign end_cnt0 = add_cnt0 && cnt0==3-1 ;

always @(posedge clk or negedge rst_n)begin

if(!rst_n) begin

cnt1 <= 0;

end

else if(add_cnt1) begin

if(end_cnt1)

&nbs p; cnt1 <= 0;

else

&nbs p; cnt1 <= cnt1 + 1;

end

end

assign add_cnt1 = end_cnt0;

assign end_cnt1 = add_cnt1 && cnt1==3-1 ;

reg           flag_add  ;

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

flag_add <= 0;

end

else if(en==1) begin

flag_add <= 1;

end

else if(end_cnt1) begin

flag_add <= 0;

end

end

reg        dout    ;

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

dout <= 0;

end

else if(add_cnt0 && cnt0==1-1)begin

dout <= 1;

end

else if(end_cnt0) begin

dout <= 0;

end

end

endmodule

本题中,我们设计了2个计数器 ,从而使得dout的设计非常简单。计数器的组合使用,对设计的复杂度有非常大的影响。合理和正确使用,将能设计出赏心悦目的代码。


1.3 至简设计法高效设计

上一节我们描述了四种情况下的设计方法。在阐述案例过程中,我们画出了大量的波形图。有读者可能会问,在工作中,我们是不是也需要先大量地画波形图,再来写代码呢?

不是的!工作中,我们要设计的系统更加的复杂,一个模块的信号也非常地多,如果我们每个模块都要画波形图,这不是明德扬提倡的至简设计。何况,功能一复杂,画出来的波形信号也是相当地多,也容易迷糊当中。

在上一节中,我们画波形图的目的,是为了让读者更清晰地理解功能、计数器和信号的关系。如果我们牢记明德扬的规则。或者熟练掌握后,波形存在心中即可。我们的设计将非常简单。

FPGA其实不是波形设计,而是功能设计,讲究的是逻辑,讲究的是因果关系。功能设计就是根据功能需求,编写我们的设计代码。我们以上一节中的案例4为例,说明什么叫功能设计。

案例4的功能要求是:当收到en=1时,dout间隔1个时钟后,产生2个时钟周期的高电平脉冲,并且重复3次。

由题意可知,要对“间隔” 和“高电平”个数进行计数,但没有信号表示“高隔”,为此想出补充一个信号 flag_add,用来表示计数区域。间隔时间+高电平时间,得到计数器数3个。

128

我们看到重复3次这一句话,这就说明还有一个计数器计数重复的次数。自然地想到,每完成一次就加1,一共加3次。得到代码如下。

129

在设计计数器0的时候,新增了信号flag_add。那进一步思考,什么时候要产生动作,那就让flag_add1。自然, 从题意可知,en==1是开始,重复次数完了,那就结束,不用再产生信号。所以flag_add代码。

1

2

3

4

5

6

7

8

9

10

11

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

flag_add <= 0;

end

else if(en==1) begin

flag_add <= 0;

end

else if(end_cnt1) begin

flag_add <= 1;

end

end

最后我们再来设计dout,由题意可知,每次均是间隔1个之后dout12个时钟之后变0。那用什么来数这个12呢?cnt0。综合起来,就是说cnt0数到1个后,dout1,数完后变0

1

2

3

4

5

6

7

8

9

10

11

always  @(posedge clk or negedge rst_n) begin

if(rst_n==1'b0) begin

dout <= 0;

end

else if(add_cnt0 && cnt0==1-1)begin

dout <= 1;

end

else if(end_cnt0) begin

dout <= 0;

end

end

总结:从功能的文字描述中出发,根据功能要求来设计代码。在设计时,一定要理解清楚信号的因果关系,例如为什么变0,为什么变1,从功能说明中找答案。经常训练这种思考和设计方式,几分钟就能设计出精妙的代码,而且因果关系、逻辑关系清楚,几乎不存在出错的可能,从而写出所想即所得的代码。



上一篇:2.9 VGA显示颜色
   拓展阅读