开发板实物图以及引脚: 开发板型号:MA703FA-35T 核心板型号:FPGA-XC7A35T-2FGG484I
CLB组成:每个CLB里面包括了2个Slices,每个Slice又是由4个LUT(查找表),8个寄存器以及一些逻辑门组成。 FPGA是以查找表的方式实现了逻辑门的等效功能。
FPGA逻辑资源容量大概可以用Slices数量来描述。
FPGA器件的容量通常用逻辑单元来衡量,这在逻辑上等同于经典的4输入LUT和触发器。
7系列FPGA逻辑单元和6输入LUT的数量之比为1.6:1,这是相对于经典的4输入LUT而言得出的。所以,逻辑单元和Slice的数量比是6.4:1。
CLB中Slices种类不同,分为两类,SLICEL与SLICEM。XILINX 7系列的FPGA中,SLICEM大致占Slices总数三分之一。
组成内容:
SLICEL与SLICEM结构的区别在查找表上,,SLICEM的查找表结构功能更复杂,增加了写操作的控制信号,以及移动位进位的功能。
查找表(LUT)实质:本质上就是一个RAM。数据事先写入RAM后,每当输入一个信号就等于输入一个地址进行查表,找出地址对应的内容输出。
可以使用下图来理解:
实现的逻辑就是,当A与B均为1时,Y为0;当A或B为0时,Y为1。
例如,A=0,此时第一级选择器接收到的信号均为1,不论B为0还是1,输出的Y都是1;B=0,此时第二级选择器接收到的信号只能是上面一个第一级选择器发送的信号,此时不论A为0还是1,输出的Y也都是1。
可实现功能:寄存器、触发器、锁存器,分布式RAM、分布式ROM,移位寄存器,多位选择器,进位逻辑等。
FPGA编程要点:
要点原因:FPGA是电路,是电路就有延迟,电路规模越大,走线越长,速度越高,可能就会超出电路的运行速度,从而产生错误。要让部分电路实现快慢有序,实现最佳的搭配。
Verilog编写的功能模块可以封装成模块或者IP,方便后面重复调用。使用关键字module与endmodule,代码写在省略号部分。
module()
...
endmodule
input 关键词,模块的输入信号;
output 关键词,模块的输出信号;
inout 模块输入输出双向信号。
input Clk; //外面关键输入的时钟信号
output [3:0]Led; //一组输出信号,其中[3:0]表示0~3,共4路信号。
wire 关键词,线信号,一般常用的线信号类型有input,output,inout,wire; reg 关键词,寄存器。和线信号不同,它可以在always中被赋值,经常用于时序逻辑中。
wire C1_Clk;
reg [3:0]Led;
语法结构:
always @ (event) begin
[multiple statements]
end
划分为时序逻辑和组合逻辑,其中区别在于敏感列表里面的内容有没有边沿事件。例如下面代码是组合逻辑:
always@(a, b, c, d) begin
out = a&b&c&d;
end
其中的a, b, c, d
是敏感列表,是触发always块内部语句的条件,当a、b、c、d中存在信号值改变,则触发always块里面的所有代码执行。
a, b, c, d
可以简化为*
替换敏感列表,表示缺省,编译器会根据always块内部的内容自动识别敏感变量。
always@(*) begin
out = a&b&c&d;
end
时序逻辑的always块将内部敏感列表包括了边沿事件,一般是时钟边沿。例如一个同步触发的D触发器:
always@(posedge i_clk) begin
if(i_rst) begin
q <= 0;
end
else begin
q <= d;
end
end
其中posedge i_clk
表示钟控信号i_clk上升沿触发D触发器,如果是negedge i_clk
,则表示下降沿触发。
敏感列表就是为了控制内部语句什么时候触发的,如果没有敏感列表,则内部语句会不断触发。当提供定时或延迟时,可以在仿真环节生成时钟。
always #10 clk = ~clk;
注意,没有敏感列表的显示延迟,是不能综合的,只能用于仿真。
assign相当于一条连线,将表达式右边的电路直接通过wire(线)连接到左边,左边信号必须是wire型(output和inout属于wire型),当右边变化了左边立马变化。可用来描述简单的组合逻辑。
wire a, b, y;
assign y = a & b;
parameter定义一个符号a为常数。
例如,定义一个符号a为十进制的170常数:
parameter a = 170; //十进制,默认分配长度32bit(编译器默认)
parameter a = 8’d170; //十进制
parameter a = 8’haa; //十六进制
parameter a = 8’b1010_1010; //二进制
“<=”赋值符号,非阻塞赋值,在一个 always 模块中,所有语句一起更新。
“=”阻塞赋值,或者给信号赋值,如果在always模块中,所有语句顺序执行。
例如以下非阻塞赋值代码:
always @(posedge clk_i or negedge rst_n_i)begin
if(!rst_n_i)begin
A <= 4'd1;
B <= 4'd2;
C <= 4'd3;
end
else begin
A <= 4'd0;
B <= A;
C <= B;
end
end
当rst_n_i为低电平时,在clk_i的上升沿,每次同时将信号A赋值为4’d0,将信号B赋值为上一周期的信号A值,将信号C赋值为上一周期的信号B值。
例如,原来某一周期,A=4'd1,B=4'd2,C=4'd3,此时rst_n_i为低电平,在clk_i上升沿,信号A、B、C同时赋值,赋值后,A=4'd0,B=4'd1,C=4'd2;再到下一周期,A=4'd0,B=4'd0,C=4'd1;再到下一周期,A=4'd0,B=4'd0,C=4'd0。
而相似的阻塞赋值代码:
always @(posedge clk_i or negedge rst_n_i)begin
if(!rst_n_i)begin
A = 4'd1;
B = 4'd2;
C = 4'd3;
end
else begin
A = 4'd0;
B = A;
C = B;
end
end
此时,当rst_n_i为低电平时,在clk_i的上升沿,先将信号A赋值为4'd0,再将信号B赋值为此时信号A的值,即4'd0,再将信号C赋值为此时信号B的值,即4'd0。 例如,原来某一周期,A=4'd1,B=4'd2,C=4'd3,此时rst_n_i为低电平,在clk_i上升沿,信号A、B、C依次赋值,赋值后,A=4'd0,然后B=A=4'd0,然后C=B=4'd0。
对比两种不同赋值的代码,可以看到相同情况下,B、C赋值结果的不同。
实际上,在描述组合逻辑电路的时候,使用阻塞赋值,比如 assign 赋值语句和不带时钟的 always 赋值语句,这种电路结构只与输入电平的变化有关系;而在描述时序逻辑的时候,使用非阻塞赋值,综合成时序逻辑的电路结构,比如带时钟的 always 语句;这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化。
常用switch case语句实现状态机。
常用的状态机描述有:一段式状态机、二段式状态机、三段式状态机。
例如如下的状态机:
使用一段式状态机描述:
module detect_1(
input clk_i,
input rst_n_i,
output [1:0] out_o
);
reg [1:0] out_r;
//状态声明和状态编码
reg [1:0] state;
parameter [1:0] S0=2'b00;
parameter [1:0] S1=2'b01;
parameter [1:0] S2=2'b10;
parameter [1:0] S3=2'b11;
always@(posedge clk_i)
begin
if(!rst_n_i)begin
state<=0;
out_r<=2'd0;
end
else
case(state)
S0 :
begin
out_r<=2'd0;
state<= S1;
end
S1 :
begin
out_r<=2'b1;
state<= S2;
end
S2 :
begin
out_r<=2'd3;
state<= S3;
end
S3 :
begin
out_r<=2'd0;
end
endcase
end
assign out_o=out_r;
//让输出out,经过寄存器out_r锁存后输出,消除毛刺
endmodule
使用二段式状态机描述:
module detect_1(
input clk_i,
input rst_n_i,
output [1:0] out_o
);
reg [1:0] out_r;
//状态声明和状态编码
reg [1:0] state;
reg [1:0] next_state;
parameter [1:0] S0=2'b00;
parameter [1:0] S1=2'b01;
parameter [1:0] S2=2'b10;
parameter [1:0] S3=2'b11;
//时序逻辑:描述状态转换
always@(posedge clk_i)
begin
if(!rst_n_i)
state<=0;
else
state<=next_state;
end
//钟控逻辑:描述各状态下赋值行为,即下一状态和输出
always@(*)
begin
case(state)
S0:
begin
out_r=2'd0;
next_state=S1;
end
S1:
begin
out_r=2'd1;
next_state=S2;
end
S2:
begin
out_r=2'd3;
next_state=S3;
end
S3:
begin
out_r=2'd0;
next_state=next_state;
end
endcase
end
assign out_o=out_r;
//让输出out,经过寄存器out_r锁存后输出,消除毛刺
endmodule
使用三段式状态机描述:
module detect_1(
input clk_i,
input rst_n_i,
output [1:0] out_o
);
reg [1:0] out_r;
//状态声明和状态编码
reg [1:0] state;
reg [1:0] next_state;
parameter [1:0] S0=2'b00;
parameter [1:0] S1=2'b01;
parameter [1:0] S2=2'b10;
parameter [1:0] S3=2'b11;
//时序逻辑:描述状态转换
always@(posedge clk_i)
begin
if(!rst_n_i)
state<=0;
else
state<=next_state;
end
//组合逻辑:描述各状态的下一状态
always@(*)
begin
case(state)
S0:
next_state=S1;
S1:
next_state=S2;
S2:
next_state=S3;
S3:
next_state=next_state;
default:
next_state=S0;
endcase
end
//组合逻辑:描述各状态下输出
always@(*)
begin
case(state)
S0,S3:
out_r<=2'd0;
S1:
out_r<=2'd1;
S2:
out_r<=2'd3;
default:
out_r<=2'd0;
endcase
end
assign out_o=out_r;
//让输出out,经过寄存器out_r锁存后输出,消除毛刺
endmodule
本文章使用limfx的vscode插件快速发布