FPGA开发学习(1)

开发板实物图以及引脚: 开发板型号:MA703FA-35T 核心板型号:FPGA-XC7A35T-2FGG484I

CLB组成:每个CLB里面包括了2个Slices,每个Slice又是由4个LUT(查找表),8个寄存器以及一些逻辑门组成。 FPGA是以查找表的方式实现了逻辑门的等效功能。

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总数三分之一。

Slice组成

组成内容:

  1. 四个逻辑函数生成器(查找表)
  2. 8 个存储单元
  3. 多功能多路复选器
  4. 快速进位逻辑

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编程要点:

  1. 不要太多的if—else 嵌套
  2. 不要编写过于庞大的状态机
  3. 复杂的状态机,可以采用2段式以上的实现
  4. 尽量使用时序逻辑完成编程
  5. 使用组合逻辑不要过于庞大
  6. 复杂代码拆分简单模块
  7. 复杂计算,增加流水设计
  8. 高速模块和低速模块搭配使用
  9. case 语句一定有default
  10. 组合逻辑有if必须有else防止产生锁存器,锁存器所以出毛刺

要点原因:FPGA是电路,是电路就有延迟,电路规模越大,走线越长,速度越高,可能就会超出电路的运行速度,从而产生错误。要让部分电路实现快慢有序,实现最佳的搭配。

Verilog语言

module

Verilog编写的功能模块可以封装成模块或者IP,方便后面重复调用。使用关键字module与endmodule,代码写在省略号部分。

  module()
  ...
  endmodule

input output

input 关键词,模块的输入信号;
output 关键词,模块的输出信号;
inout 模块输入输出双向信号。

input Clk;        //外面关键输入的时钟信号
output [3:0]Led;  //一组输出信号,其中[3:0]表示0~3,共4路信号。 

wire reg

wire 关键词,线信号,一般常用的线信号类型有input,output,inout,wire; reg 关键词,寄存器。和线信号不同,它可以在always中被赋值,经常用于时序逻辑中。

wire C1_Clk;
reg [3:0]Led; 

always

语法结构:

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

assign相当于一条连线,将表达式右边的电路直接通过wire(线)连接到左边,左边信号必须是wire型(output和inout属于wire型),当右边变化了左边立马变化。可用来描述简单的组合逻辑。

wire a, b, y;  
assign y = a & b; 

parameter

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 语句;这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化。

FPGA中的状态机

常用switch case语句实现状态机。
常用的状态机描述有:一段式状态机、二段式状态机、三段式状态机。

例如如下的状态机:

  1. 有4种状态:S0、S1、S2、S3;
  2. 状态转换逻辑:S0->S1->S2->S3;
  3. 不同状态下输出信号out_p的值不同:状态S0、S3下,out_o=2'd0;状态S1下,out_o=2'd1;状态S2下,out_o=2'd3。

使用一段式状态机描述:

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 

本周工作内容

  • 阅读学长论文,了解研究方向与研究内容
  • 阅读相关资料,学习FPGA开发
  • 尝试练习使用Vivado

后续计划

  • 继续学习FPGA开发相关知识
  • 结合Vivado使用,应用练习FPGA开发

本文章使用limfx的vscode插件快速发布