基于Altera Quartus II的模块化设计应用
0赞和ISE不一样,Quartus II代码的模块化视图并不是你把子模块例化到顶层模块以后就能马上在工程代码窗口看到一个清晰的层次图。而是必须在你编译后才能够看到层次化的视图。
我在这里列举一个串口通信(实验10)的实例:
编写完该HDL的代码,还没有编译,此时在Project Navigator窗口中只有顶层模块my_uart_top。
顶层模块如下(详细的注释代码请参考相关实验):
////////////////////////////////////////////////////////////////////////////////
module my_uart_top(
clk,rst_n,
rs232_rx,rs232_tx
);
input clk;
input rst_n;
input rs232_rx;
output rs232_tx;
wire bps_start1,bps_start2;
wire clk_bps1,clk_bps2;
wire[7:0] rx_data;
wire rx_int;
////////////////////////////////////////////
speed_select speed_rx(
.clk(clk), .rst_n(rst_n),
.bps_start(bps_start1),
.clk_bps(clk_bps1)
);
my_uart_rx my_uart_rx(
.clk(clk), .rst_n(rst_n),
.rs232_rx(rs232_rx),
.rx_data(rx_data),
.rx_int(rx_int),
.clk_bps(clk_bps1),
.bps_start(bps_start1)
);
///////////////////////////////////////////
speed_select speed_tx(
.clk(clk),
.rst_n(rst_n),
.bps_start(bps_start2),
.clk_bps(clk_bps2)
);
my_uart_tx my_uart_tx(
.clk(clk),
.rst_n(rst_n),
.rx_data(rx_data),
.rx_int(rx_int),
.rs232_tx(rs232_tx),
.clk_bps(clk_bps2),
.bps_start(bps_start2)
);
Endmodule
我们一般不在顶层模块做任何逻辑设计,哪怕只是一个逻辑与操作。比较好的设计会明确的区分每一个模块单元。在上面这个设计中,是要实现一个串口自收发通信的功能。具体说就是不断的检测串口接收信号rs232_rx是否有数据,如果接收到起始位就把数据保存,然后再转手把接收到的数据通过串口发送信号rs232_tx发回给对方。即使是这样一个还不算太复杂的功能,如果都堆到一个模块里,代码不仅又臭又长,编写代码者如果不理好思路很容易自己就写晕了,以后维护起来或者要移植就更难了。
所以,模块化的设计势在必行。上面的代码把这个设计分成了四个模块:
1、My_uart_tx:串口数据接收模块;
2、Speed_tx:串口数据接收时钟校准模块;
3、My_uart_rx:串口数据发送模块;
4、Speed_rx:串口数据发送时钟校准模块。
如此划分,层次清晰而且思路明确,写起代码来更是游刃有余。先来说模块例化的一些细节吧。就拿speed_select模块例化来看。第一行的speed_select speed_rx,其中speed_select是要例化的模块名,是固定的;而speed_rx则是你任意给这个模块取的名字,它是用于区分例化多个相同的模块。就如speed_tx和speed_rx两个模块,因为它们的逻辑设计都是一样的,所以写一个模块,然后在例化的时候给个不同的名称就可以了。这有点类似软件设计中的子程序调用,但又有不同,由于硬件设计的并行性,这里的逻辑复制实际上在最后的硬件上是实现了两个一模一样的speed_select设计原型,可以说它们是完全独立的。即便是对于硬件资源的消耗没有减少,采用模块化设计以后也能从很大程度上减少设计者的重复劳动。
信号的例化是这样的.clk(clk),点号后的clk代表例化模块内部的信号(是固定的,必须和内部的信号名一致),而括号内的clk则是例化模块的外部连接,可以例化模块内的信号名不同。
在编译后,可以从Project Navigator窗口中看到例化的子模块:
另外,从Quartus II提供的RTL视图里,我们能够更深刻的感受到模块化带来的层次感: