立即注册 登录
一牛网 返回首页

H18523907097的个人空间 http://bbs.16rd.com/?93554 [收藏] [复制] [RSS]

日志

做FPGA没有思路?我来帮你!

已有 346 次阅读2019-8-17 09:40 |系统分类:嵌入式论坛| FPGA

1.1 看波形图的方法

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

&nBSP;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,这是明德扬的至简设计方法中的变量法。

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

等待下文


路过

鸡蛋

鲜花

握手

雷人

评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 立即注册

联系我们|小黑屋