首先得承认,我并不是主动拥抱顶层文件这套思路的,原因很简单,能用就行干嘛费劲搞那么多东西。起初知识点亮一个LED灯,整一个半加器的简单模拟,也确实根本用不上。后边工程有一定的负责度,例如设计数字时钟,LCD1602驱动设计等等,这个时候我就发现了层次化设计的一个便捷之处,在于他们方便复用
,只需要定义好一个功能Module,可以在仿真 – 下板之间无缝衔接,增加了自己开发的效率,减少不必要的注释,感兴趣可以接着往下看。
另外自己上述两个工程博客连接:
一图胜千言,引用野火开发板他们家的资料(文末备注)里说明复用
的思想在哪个地方。举个例子,比如我现在定义了一个橙色的模块,我现在第一层(绿色)的需求是设计一个数字时钟,那么第二层拆分开就是定时1秒(蓝色),定时1分钟(橙色)、定时1小时(红色)。接着蓝色的定时1秒,我再通过其他小模块继续拆分。那么大家有没有想过一个问题,都是同样的计数/计时,秒钟/分钟/时钟不都是一样的吗,只不过一个是定时1秒,一个是60秒,一个是3600秒。是的,我们完全可以统一写一个定时模块(比如第三层蓝色功能模块1_1),改变定时的值(1/6/3600)即可。
也就是说,层次化的设计思想,其实就是拆分需求,减少重复代码统一集成一个公用的模块,这就达到了Module复用
的功能。说起来非常高端,我们通过一个简单例子来说明下如何使用吧,时间才是检验真理的唯一标准。
首先要申明一点,目前入门FPGA的阶段属于非常非常,所有工程代码、工程逻辑都可能存在错误,希望各位同学老师能够指出,同时也参考了大量的互联网上优质内容,在这里一并感谢,基于这个原因也决定将核心逻辑进行共享。请勿做一切商业用途。接下来会以我在数字时钟那篇的博客,讲一讲大概的逻辑,具体实操大家可以移步数字时钟的那篇博客。
这里是引用
带大家看三个文件,第一个是功能模块长什么样;第二个是理解顶层文件Top.v长什么样,到底是怎么复用的;第三个是了解testbench文件是如何设计的,和顶层文件有什么区别。
clock1.v文件:
//数字时钟模块
module clock1(
sec,
min,
hour,
rst,
sys_clk_p,
sys_clk_n,
clk_out1,
led_out1,
led_out2
);
input rst;
input sys_clk_p; // Differential input clock 200Mhz
input sys_clk_n; // Differential input clock 200Mhz
output clk_out1;
output [7:0] sec,min;
output [7:0] hour;
output led_out1;
output led_out2;
reg [7:0] sec=0;
reg [7:0] min=0;
reg [7:0] hour=0;
reg [32:0] cnt;
reg clk_div;
reg led_out1;
reg led_out2;
//分频部分 50MHz - 1Hz
always @ ( posedge clk_out1 or posedge rst)begin
篇幅原因略去代码
end
// 秒钟部分
always @ ( posedge clk_div or posedge rst)begin
篇幅原因略去代码
end
//分钟部分
always @ ( posedge clk_div or posedge rst)begin
篇幅原因略去代码
end
// 时钟部分
always @ ( posedge clk_div or posedge rst)
篇幅原因略去代码
end
endmodule
这个模块该有的功能全都有,篇幅原因省去的代码其实都基本一个样。需要关注到的是这个module里边详细定义了sec
、min
、hour
这三个变量,并且都是output
类型。因为我们是对顶层设计的举例子,代码详细功能不在这里讲解。接下来一并看剩下两个文件。
Top.v文件
module TOP(
input rst,
input sys_clk_p,
input sys_clk_n,
output clk_out1,
output led_out1,
output led_out2
);
//***********差分时钟50MHz *****************************************
wire sys_clk_p;
wire sys_clk_n;
wire rst;
wire clk_out1;
//***********数字时钟****************************************
wire led_out1;
wire led_out2;
//时钟模块初始化
clock1 clock(
.led_out1(led_out1),
.led_out2(led_out2),
.rst(rst),
.sys_clk_p(sys_clk_p ),
.sys_clk_n(sys_clk_n ),
.clk_out1(clk_out1 )
);
//PLL分频的代码
endmodule
Testbench文件
`timescale 1ns / 1ns
module clock1_tb();
//***********??????*****************************************
reg sys_clk_p;
wire sys_clk_n;
reg rst;
wire clk_out1;
//***********??????*****************************************
wire [7:0] sec;
wire [7:0] min;
wire [7:0] hour;
wire led_out1;
wire led_out2;
//初始化系统时钟
initial begin
sys_clk_p = 1'b0;
略去代码
end
//数字时钟初始化
clock1 clock(
.led_out1(led_out1),
.led_out2(led_out2),
.sec(sec),
.min(min),
.hour(hour),
.rst(rst),
.sys_clk_p(sys_clk_p ),
.sys_clk_n(sys_clk_n ),
.clk_out1(clk_out1 )
);
//PLL时钟分频
endmodule
可以看两个文件夹,代码基本都是一模一样的,定义好端口(input/output/wire/reg等),初始化clock模块如何结束。
这里重点关注第一点,我们这里并不需要把sec
、min
、hour
这三个寄存器变量输出(需要制定引脚),所以我们在Top.v文件里边不声明他的输入输出类型,但实际下板却并不影响clock功能的实现,因为模块的功能实现与否,和你是否声明并无关系。你申明了,他就展示出来,不声明他就不展示出来,但功能一直都在实现。
这样写的一个好处就是,我们只需要写好一个module文件,在仿真测试,以及下板验证的时候都能够很快、很简单地实现,而不用大量复制代码,在两头切换。
补充
在testbench文件我们对他进行声明了,当然了,因为我们仿真的时候需要这个变量来看看,初步确认他的功能是好的,实际下板我们并不需要把秒、分、时都输出出来(设想一个寄存器变量占8位,3个就是24位,并没有这么多引脚给你占用)。当然如果你有意为之,你也可以如下设置这三个变量,那么你便需要设置相关引脚并进行下一步。
此时的Top.v文件
module TOP(
output sec,
output min,
output hour,
…………
同上
);
wire [7:0] sec;
wire [7:0] min;
wire [7:0] hour;
//***********差分时钟50MHz *****************************************
同上
//***********数字时钟****************************************
同上
//时钟模块初始化
同上
//PLL分频的代码
同上
endmodule
不声明,但功能实现时候,引脚的截图:
Testbench也可以理解为一种顶层文件
);以上。
如果你觉得这篇文章对你有帮助,请为我点个赞谢谢
如果有遇到其他问题,也请在评论区留言