Table of contents
Open Table of contents
1 综合器 & 仿真器
综合器(Synthesizer):当 Verilog 描述出硬件功能后,我们需要综合器对 Verilog 代码进行解释,将代码转化成实际的电路来表示,最终实际的电路,我们称之为网表。Quartus、ISE 和 Vivado 都是综合器,集成电路常用的综合器是 DC。
仿真器(Simulator):在 FPGA 设计的过程中,不可避免会出现各种 BUG。如果我们编写好代码,综合成电路,烧写到 FPGA 后才看到问题,此时去定位问题就会非常地困难。在综合前,我们可以在电脑里对代码进行仿真测试一下,把 BUG 找出来解决,最后才烧写进 FPGA。常用的仿真器是 Modelsim 和 VCS 等。
注意: 仿真器只是对代码进行仿真验证。至于该电路是否可转成电路,仿真器是不关心的。
2 Altera & Xilinx
Altera 的FPGA产品主要包括Stratix系列和Cyclone系列。Stratix系列是其高端产品,提供了更高的性能和功能,适用于需要高性能的应用场景。而Cyclone系列则是低成本、低功耗的产品,适用于中小规模的应用。Altera的开发软件是 Quartus Prime 。
Xilinx 的FPGA产品主要包括Virtex系列和Artix系列。Virtex系列是Xilinx的高端产品,提供了最高的性能和功能,适用于要求最高性能的应用场景。而Artix系列则是低成本、低功耗的产品,适用于中小规模的应用。Xilinx的开发软件是 Vivado 。
完成一个 Verilog 项目,需要使用对应 FPGA 产品的开发软件,创建一个例程。
3 D触发器
数字电路介绍了多种触发器:JK触发器、D触发器、RS触发器、T触发器等。在 FPGA 中,我们使用的是最简单的触发器——D触发器。
D触发器可以看作一个芯片,拥有四个管脚。三个输入:时钟clk、复位rst_n、信号d,一个输出:信号q。
+-------+
| |
d ------> | D | ------> q
clk ----> | |
+-------+
|
rst_n --------+
其功能为:rst_n低电平时,q也为低电平;rst_n高电平时,再看管脚clk,在 clk 由 0 变 1 (上升沿)的时候,,将现在d的值赋给q。时序逻辑代码如下:
always @(posedge clk or negedge rst_n) begin
if(rst_n==1'b0) begin
q <= 0;
end
else begin
q <= d;
end
end
注意:
- 在
clk时钟上升沿的时候,此时变化的信号q的值为多少?
我们想一下代码的因果关系。是先有时钟上升沿,这个是因。然后将
d的值赋给q,这个是结果。对于硬件来说,这个“先后”无论是多么地快,也是占有一定时间的,所以q的变化会稍后于clk的上升沿。故q为 0 。但这样画图实在是没累了,而且也没有完成必要,只需掌握这种波形规则。
- 复位信号是在系统开始时刻或者出现异常时才使用,一般上电后就不会再次复位了,可以认为复位是特殊的情况。
4 至简设计法
4.1 类型一
案例:当收到en=1后,dout产生一个宽度为 10 个时钟周期的高电平脉冲。
推理:
- 从功能要求中,看到数字 10,我们就知道要计数,要使用计数器;
- 10 个是指
dout==1的次数为 10 个时钟周期,所以该计数器数的是dout==1的次数, 因此看到dout==1时,计数器就会加 1。
计数器原则:
- 初值为零:复位后,计数器一定要为零;
- 最终清零:数到最后时,计数器要清零;
计数器代码:
/* cnt */
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt <= 0;
end
else if(add_cnt) begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
/* 加一条件 */
assign add_cnt = dout==1;
/* 结束条件 */
assign end_cnt = add_cnt && cnt==10-1;
注意:
add_cnt && cnt==x-1表示“数到第x个的时候”
设计好计数器 cnt 后,我们就可以设计输出信号 dout 了。该信号有两个变化点:
- 变 1:是由于收到
en==1; - 变 0:数到了 10 个或者是数完了。
输出信号代码:
/* dout */
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dout <= 0;
end
else if(en==1) begin
dout <= 1;
end
else if(end_cnt) begin
dout <= 0;
end
end
补充 module 的其他部分:cnt计数的最大值为 9,需要用 4 根线表示,即位宽是 4 位。add_cnt和end_cnt都是用assign方式设计的,因此类型为 wire。dout是用always方式设计的,因此类型为reg。
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;
// ...
endmodule
4.2 类型二
案例:当收到en=1后,dout间隔 3 个时钟后,产生宽度为 2 个时钟周期的高电平脉冲。
推理:
- 出现大于 1 的数字时,就需要计数。这里有连续的数字 2 和 3,建议的计数方式为 5 ;
- 没有信号明确计数时,补充
flag_add信号。
计数器代码:补充该信号后,计数器的加 1 条件就变为 flag_add==1:
/* cnt */
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt <= 0;
end
else if(add_cnt) begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
/* 加一条件 */
assign add_cnt = flag_add==1;
/* 结束条件 */
assign end_cnt = add_cnt && cnt==5-1;
flag_add有 2 个变化点:
- 变 1:条件是收到
en==1; - 变 0:的条件是计数器数完了。
/* flag_add */
always @(posedge clk or negedge rst_n) begin
if(!rst_n) 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 :条件是“3 个间隔之后”
- 变 0 :条件是数完了
/* dout */
always @(posedge clk or negedge rst_n) begin
if(!rst_n) 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 的其他部分:cnt 是用 always 产生的信号,因此类型为 reg。cnt 计数的最大值为 4,需要用 3 根线表示,即 位宽是 3 位。add_cnt 和 end_cnt 都是用 assign 方式设计的,因此类型为 wire。flag_add、dout 是用 always 方式设计的,因此类型为 reg。
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;
// ...
endmodule