FPGA实现YOLOv3 tiny 代码分析

目录

总体概览:​编辑

卷积加速器部分代码:

 视频接收与输出部分Block design结构

global_para_gen

代码

疑问

interface_axilite_ctrl

代码 

generate_ctrl_signal

代码

引用模块 com_ctrl_task

疑问

interface_axis_slave

代码

 疑问

interface_axis_master

代码

疑问

axis_buf_sel

代码

global_data_beat(头疼!看不懂md

代码

引用模块

疑问



总体概览:

卷积加速器部分代码:

 视频接收与输出部分Block design结构

global_para_gen

代码

module global_para_gen	//生成不同卷积层输出特征图地址
#(


	parameter FM_ADDR_BIT=12,		// 特征图地址位数
	parameter LINEBUFFER_LEN1=16,  // 第一层缓存线长度 上一层实际设置位15、13、26、52、104、208
	parameter LINEBUFFER_LEN2=14,  // 第二层缓存线长度 
	parameter LINEBUFFER_LEN3=28,  // 第三层缓存线长度
	parameter LINEBUFFER_LEN4=56,  // 第四层缓存线长度
	parameter LINEBUFFER_LEN5=112,  // 第五层缓存线长度
	parameter LINEBUFFER_LEN6=224	// 第六层缓存线长度 
)
(
	input clk,  // 时钟信号
	input [2:0] sel,  // 选择信号,表示选择哪一层卷积层和池化层
	input [7:0] row,  // 行数
	input [1:0] ofm_send_sel,  // 输出特征图选择信号
	input pool_enable,  // 池化使能信号


	output [8:0] conv_row,  // 卷积行数
	output reg [8:0] conv_col,  // 卷积列数
	output [8:0] pool_row,  // 池化行数
	output reg [8:0] pool_col,  // 池化列数
	
	output [FM_ADDR_BIT-1:0] conv_addr_len,  // 卷积地址长度
	output [FM_ADDR_BIT-1:0] pool_addr_len,  // 池化地址长度
	output reg [FM_ADDR_BIT-1:0] ofm_addr_start,  // 输出特征图起始地址
	output reg [FM_ADDR_BIT-1:0] ofm_addr_end  // 输出特征图结束地址
);

// 定义特征图各层列数的常量
	localparam [8:0] FM_COL_0=LINEBUFFER_LEN1;
	localparam [8:0] FM_COL_1=LINEBUFFER_LEN1+LINEBUFFER_LEN2;
	localparam [8:0] FM_COL_2=LINEBUFFER_LEN1+LINEBUFFER_LEN2+LINEBUFFER_LEN3;
	localparam [8:0] FM_COL_3=LINEBUFFER_LEN1+LINEBUFFER_LEN2+LINEBUFFER_LEN3+LINEBUFFER_LEN4;
	localparam [8:0] FM_COL_4=LINEBUFFER_LEN1+LINEBUFFER_LEN2+LINEBUFFER_LEN3+LINEBUFFER_LEN4+LINEBUFFER_LEN5;
	localparam [8:0] FM_COL_5=LINEBUFFER_LEN1+LINEBUFFER_LEN2+LINEBUFFER_LEN3+LINEBUFFER_LEN4+LINEBUFFER_LEN5+LINEBUFFER_LEN6;
	
	always@(*) begin// 根据选择的层来设置卷积和池化的列数
		case(sel)
			3'b000:	begin
				conv_col<=FM_COL_0;
				pool_col<=FM_COL_0;
			end
			3'b001:	begin
				conv_col<=FM_COL_1;
				pool_col<=FM_COL_0;
			end
			3'b010:	begin
				conv_col<=FM_COL_2;
				pool_col<=FM_COL_1;
			end
			3'b011:	begin
				conv_col<=FM_COL_3;
				pool_col<=FM_COL_2;
			end
			3'b100:	begin
				conv_col<=FM_COL_4;
				pool_col<=FM_COL_3;
			end
			3'b101:	begin
				conv_col<=FM_COL_5;
				pool_col<=FM_COL_4;
			end
			default: begin
				conv_col<=0;
				pool_col<=0;
			end
		endcase
	end
	wire [8:0] pool_row_t1;
	wire [8:0] pool_row_t2;
	wire [8:0] pool_row_t3;
	assign conv_row={1'b0,row};				//为什么这部分的行数是输入的而不等于列数
	assign pool_row_t1=conv_row-2;
	assign pool_row_t2={1'b0,pool_row_t1[8:1]};
	assign pool_row=pool_row_t2+2;
	
	assign conv_addr_len=conv_row*conv_col;
	assign pool_addr_len=pool_row*pool_col;
	
	reg [FM_ADDR_BIT-1:0] no_pool_ofm_addr_start;
	reg [FM_ADDR_BIT-1:0] no_pool_ofm_addr_end;
	reg [FM_ADDR_BIT-1:0] pool_ofm_addr_start;
	reg [FM_ADDR_BIT-1:0] pool_ofm_addr_end;
	
	// 根据不同的ofm_send_sel设置不同的地址起始和结束值
	always@(posedge clk) begin
		case(ofm_send_sel)
			2'b00: begin // whole
				no_pool_ofm_addr_start<=0;
				no_pool_ofm_addr_end<=conv_addr_len;
				pool_ofm_addr_start<=0;
				pool_ofm_addr_end<=pool_addr_len;
			end
			2'b01: begin // no head
				no_pool_ofm_addr_start<=conv_col;
				no_pool_ofm_addr_end<=conv_addr_len;
				pool_ofm_addr_start<=pool_col;
				pool_ofm_addr_end<=pool_addr_len;
			end
			2'b10: begin // no tail
				no_pool_ofm_addr_start<=0;
				no_pool_ofm_addr_end<=conv_addr_len-conv_col;
				pool_ofm_addr_start<=0;
				pool_ofm_addr_end<=pool_addr_len-pool_col;
			end
			2'b11: begin // no head no tail
				no_pool_ofm_addr_start<=conv_col;
				no_pool_ofm_addr_end<=conv_addr_len-conv_col;
				pool_ofm_addr_start<=pool_col;
				pool_ofm_addr_end<=pool_addr_len-pool_col;
			end
			default: begin
				no_pool_ofm_addr_start<=0;
				no_pool_ofm_addr_end<=conv_addr_len;
				pool_ofm_addr_start<=0;
				pool_ofm_addr_end<=pool_addr_len;
			end
		endcase
	end
	// 根据池化使能信号来选择使用哪个地址范围
	always@(posedge clk) begin
		case(pool_enable)
		1'b1: begin
			ofm_addr_start<=pool_ofm_addr_start;
			ofm_addr_end<=pool_ofm_addr_end;
		end
		1'b0: begin
			ofm_addr_start<=no_pool_ofm_addr_start;
			ofm_addr_end<=no_pool_ofm_addr_end;
		end
		default: begin
			ofm_addr_start<=0;
			ofm_addr_end<=0;
		end
		endcase
	end
endmodule

疑问

1.

	parameter FM_ADDR_BIT=12,		// 特征图地址位数
	parameter LINEBUFFER_LEN1=16,  // 第一层缓存线长度 
	parameter LINEBUFFER_LEN2=14,  // 第二层缓存线长度//13+1
	parameter LINEBUFFER_LEN3=28,  // 第三层缓存线长度//26+2
	parameter LINEBUFFER_LEN4=56,  // 第四层缓存线长度//52+4
	parameter LINEBUFFER_LEN5=112,  // 第五层缓存线长度//104+8	
	parameter LINEBUFFER_LEN6=224	// 第六层缓存线长度   //208+16猜测应该是符号位?或者13个bit每个需要一个head

后续每层的缓存长度应该和yolo v3的层数的行列数有关,但是第一层的缓存线长度为什么是16还没有搞清楚,后续看完整个代码再来补充。

 2.

	assign conv_row={1'b0,row};				//为什么这部分的行数是输入的而不等于列数
	assign pool_row_t1=conv_row-2;
	assign pool_row_t2={1'b0,pool_row_t1[8:1]};
	assign pool_row=pool_row_t2+2;

这部分也没搞清楚,为什么行数是外部输入,而不和列数相等,池化采用2*2,池化行数应该是列数的一半,但为什么要-2后÷2再+2?

3.

// 根据不同的ofm_send_sel设置不同的地址起始和结束值
	always@(posedge clk) begin
		case(ofm_send_sel)
			2'b00: begin // whole
				no_pool_ofm_addr_start<=0;
				no_pool_ofm_addr_end<=conv_addr_len;
				pool_ofm_addr_start<=0;
				pool_ofm_addr_end<=pool_addr_len;
			end
			2'b01: begin // no head
				no_pool_ofm_addr_start<=conv_col;
				no_pool_ofm_addr_end<=conv_addr_len;
				pool_ofm_addr_start<=pool_col;
				pool_ofm_addr_end<=pool_addr_len;
			end
			2'b10: begin // no tail
				no_pool_ofm_addr_start<=0;
				no_pool_ofm_addr_end<=conv_addr_len-conv_col;
				pool_ofm_addr_start<=0;
				pool_ofm_addr_end<=pool_addr_len-pool_col;
			end
			2'b11: begin // no head no tail
				no_pool_ofm_addr_start<=conv_col;
				no_pool_ofm_addr_end<=conv_addr_len-conv_col;
				pool_ofm_addr_start<=pool_col;
				pool_ofm_addr_end<=pool_addr_len-pool_col;
			end
			default: begin
				no_pool_ofm_addr_start<=0;
				no_pool_ofm_addr_end<=conv_addr_len;
				pool_ofm_addr_start<=0;
				pool_ofm_addr_end<=pool_addr_len;
			end
		endcase

头尾,没头没尾分别是在什么情况下被调用?

interface_axilite_ctrl

代码 


`timescale 1 ns / 1 ps

	module interface_axilite_ctrl #
	(
			// 用户在这里添加参数
			// Users to add parameters here
		
			// 用户参数结束
			// User parameters ends
			// 不要修改此行以下的参数
			// Do not modify the parameters beyond this line
		
			// S_AXI数据总线的宽度
			// Width of S_AXI data bus
			parameter integer C_S_AXI_DATA_WIDTH	= 32,
			// S_AXI地址总线的宽度
			// Width of S_AXI address bus
			parameter integer C_S_AXI_ADDR_WIDTH	= 4
		)
		(
			// 用户在这里添加端口
			// Users to add ports here
			output reg [C_S_AXI_DATA_WIDTH-1:0] reg_0,
			output reg [C_S_AXI_DATA_WIDTH-1:0] reg_1,
			output reg [C_S_AXI_DATA_WIDTH-1:0] reg_2,
			output reg [C_S_AXI_DATA_WIDTH-1:0] reg_3,
			// 用户端口结束
			// User ports ends
			// 不要修改此行以下的端口
			// Do not modify the ports beyond this line
		
			// 全局时钟信号
			// Global Clock Signal
			input wire  clk,
			// 全局复位信号,此信号为低电平有效
			// Global Reset Signal. This Signal is Active LOW
			input wire  rst,
			// 写地址(由主设备发出,从设备接收)
			// Write address (issued by master, acceped by Slave)
			input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR,
			// 写通道保护类型。此信号指示事务的特权和安全级别,以及事务是数据访问还是指令访问。
			// Write channel Protection type. This signal indicates the
			// privilege and security level of the transaction, and whether
			// the transaction is a data access or an instruction access.
			input wire [2 : 0] S_AXI_AWPROT,
			// 写地址有效。此信号表示主设备发出有效的写地址和控制信息。
			// Write address valid. This signal indicates that the master signaling
			// valid write address and control information.
			input wire  S_AXI_AWVALID,
			// 写地址就绪。此信号表示从设备已准备好接受地址和相关的控制信号。
			// Write address ready. This signal indicates that the slave is ready
			// to accept an address and associated control signals.
			output wire  S_AXI_AWREADY,
			// 写数据(由主设备发出,从设备接收)
			// Write data (issued by master, acceped by Slave)
			input wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA,
			// 写选通。此信号表示哪些字节通道持有有效数据。写数据总线的每八位有一个写选通位。
			// Write strobes. This signal indicates which byte lanes hold
			// valid data. There is one write strobe bit for each eight
			// bits of the write data bus.
			input wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB,
			// 写有效。此信号表示有效的写数据和选通是可用的。
			// Write valid. This signal indicates that valid write
			// data and strobes are available.
			input wire  S_AXI_WVALID,
			// 写就绪。此信号表示从设备可以接受写数据。
			// Write ready. This signal indicates that the slave
			// can accept the write data.
			output wire  S_AXI_WREADY,
			// 写响应。此信号表示写事务的状态。
			// Write response. This signal indicates the status
			// of the write transaction.
			output wire [1 : 0] S_AXI_BRESP,
			// 写响应有效。此信号表示通道正在发出有效的写响应。
			// Write response valid. This signal indicates that the channel
			// is signaling a valid write response.
			output wire  S_AXI_BVALID,
			// 响应就绪。此信号表示主设备可以接受写响应。
			// Response ready. This signal indicates that the master
			// can accept the write response.
			input wire  S_AXI_BREADY,
			// 读地址(由主设备发出,从设备接收)
			// Read address (issued by master, acceped by Slave)
			input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR,
			// 读通道保护类型。此信号指示事务的特权和安全级别,以及事务是数据访问还是指令访问。
			// Read channel Protection type. This signal indicates the
			// privilege and security level of the transaction, and whether
			// the transaction is a data access or an instruction access.
			input wire [2 : 0] S_AXI_ARPROT,
			// 读地址有效。此信号表示主设备发出有效的读地址和控制信息。
			// Read address valid. This signal indicates that the master
			// signaling valid read address and control information.
			input wire  S_AXI_ARVALID,
			// 读地址就绪。此信号表示从设备已准备好接受地址和相关的控制信号。
			// Read address ready. This signal indicates that the slave
			// is ready to accept an address and associated control signals.
			output wire  S_AXI_ARREADY,
			// 读数据(由从设备发出,主设备接收)
			// Read data (issued by Slave, accepted by master)
			output wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA,
			// 读响应。此信号表示读事务的状态。
			// Read response. This signal indicates the status
			// of the read transaction.
			output wire [1 : 0] S_AXI_RRESP,
			// 读有效。此信号表示通道正在发出有效的读数据。
			// Read valid. This signal indicates that the channel
			// is signaling valid read data.
			output wire  S_AXI_RVALID,
			// 读就绪。此信号表示主设备可以接受读数据。
			// Read ready. This signal indicates that the master
			// can accept the read data.
			input wire  S_AXI_RREADY
		);

	// AXI4LITE signals
	reg [C_S_AXI_ADDR_WIDTH-1 : 0] 	axi_awaddr;
	reg  	axi_awready;
	reg  	axi_wready;
	reg [1 : 0] 	axi_bresp;
	reg  	axi_bvalid;
	reg [C_S_AXI_ADDR_WIDTH-1 : 0] 	axi_araddr;
	reg  	axi_arready;
	reg [C_S_AXI_DATA_WIDTH-1 : 0] 	axi_rdata;
	reg [1 : 0] 	axi_rresp;
	reg  	axi_rvalid;

	// Example-specific design signals
	// local parameter for addressing 32 bit / 64 bit C_S_AXI_DATA_WIDTH
	// ADDR_LSB is used for addressing 32/64 bit registers/memories
	// ADDR_LSB = 2 for 32 bits (n downto 2)
	// ADDR_LSB = 3 for 64 bits (n downto 3)
	localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32) + 1;
	localparam integer OPT_MEM_ADDR_BITS = 1;
	//----------------------------------------------
	//-- Signals for user logic register space example
	//------------------------------------------------
	//-- Number of Slave Registers 4
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg0;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg1;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg2;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg3;
	wire	 slv_reg_rden;
	wire	 slv_reg_wren;
	reg [C_S_AXI_DATA_WIDTH-1:0]	 reg_data_out;
	integer	 byte_index;
	reg	 aw_en;

	// I/O Connections assignments

	assign S_AXI_AWREADY	= axi_awready;
	assign S_AXI_WREADY	= axi_wready;
	assign S_AXI_BRESP	= axi_bresp;
	assign S_AXI_BVALID	= axi_bvalid;
	assign S_AXI_ARREADY	= axi_arready;
	assign S_AXI_RDATA	= axi_rdata;
	assign S_AXI_RRESP	= axi_rresp;
	assign S_AXI_RVALID	= axi_rvalid;

	// 这个always block是用来处理写地址阶段的。
	always @( posedge clk )
	begin
	  if ( rst == 1'b1 )
	    begin
	      axi_awready <= 1'b0;
	      aw_en <= 1'b1;
	    end 
	  else
	    begin    
	      if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)
	        begin
	          axi_awready <= 1'b1;
	          aw_en <= 1'b0;
	        end
	        else if (S_AXI_BREADY && axi_bvalid)
	            begin
	              aw_en <= 1'b1;
	              axi_awready <= 1'b0;
	            end
	      else           
	        begin
	          axi_awready <= 1'b0;
	        end
	    end 
	end       

	// 这个always block是用来存储写地址的。
	always @( posedge clk )
	begin
	  if ( rst == 1'b1 )
	    begin
	      axi_awaddr <= 0;
	    end 
	  else
	    begin    
	      if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)
	        begin
	          // Write Address latching 
	          axi_awaddr <= S_AXI_AWADDR;
	        end
	    end 
	end       

	// 这个always block是用来处理写数据阶段的。
	always @( posedge clk )
	begin
	  if ( rst == 1'b1 )
	    begin
	      axi_wready <= 1'b0;
	    end 
	  else
	    begin    
	      if (~axi_wready && S_AXI_WVALID && S_AXI_AWVALID && aw_en )
	        begin
	          axi_wready <= 1'b1;
	        end
	      else
	        begin
	          axi_wready <= 1'b0;
	        end
	    end 
	end       
	assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;

	// 这个always block是用来处理写数据到寄存器的。
	always @( posedge clk )
	begin
	  if ( rst == 1'b1 )
	    begin
	      slv_reg0 <= 0;
	      slv_reg1 <= 0;
	      slv_reg2 <= 0;
	      slv_reg3 <= 0;
	    end 
	  else begin
	    if (slv_reg_wren)
	      begin
	        case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
	          2'h0:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin //写数据总线的每八位有一个写选通位。
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 0
	                slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          2'h1:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 1
	                slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          2'h2:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 2
	                slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          2'h3:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 3
	                slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          default : begin
	                      slv_reg0 <= slv_reg0;
	                      slv_reg1 <= slv_reg1;
	                      slv_reg2 <= slv_reg2;
	                      slv_reg3 <= slv_reg3;
	                    end
	        endcase
	      end
	  end
	end    
	
	// 这个always block是用来生成写响应的。
	always @( posedge clk )
	begin
	  if ( rst == 1'b1 )
	    begin
	      axi_bvalid  <= 0;
	      axi_bresp   <= 2'b0;
	    end 
	  else
	    begin    
	      if (axi_awready && S_AXI_AWVALID && ~axi_bvalid && axi_wready && S_AXI_WVALID)
	        begin
	          // indicates a valid write response is available
	          axi_bvalid <= 1'b1;
	          axi_bresp  <= 2'b0; // 'OKAY' response 
	        end                   // work error responses in future
	      else
	        begin
	          if (S_AXI_BREADY && axi_bvalid) 
	            //check if bready is asserted while bvalid is high) 
	            //(there is a possibility that bready is always asserted high)   
	            begin
	              axi_bvalid <= 1'b0; 
	            end  
	        end
	    end
	end   

	// 这个always block是用来处理读地址阶段的。
	always @( posedge clk )
	begin
	  if ( rst == 1'b1 )
	    begin
	      axi_arready <= 1'b0;
	      axi_araddr  <= 32'b0;
	    end 
	  else
	    begin    
	      if (~axi_arready && S_AXI_ARVALID)
	        begin
	          // indicates that the slave has acceped the valid read address
	          axi_arready <= 1'b1;
	          // Read address latching
	          axi_araddr  <= S_AXI_ARADDR;
	        end
	      else
	        begin
	          axi_arready <= 1'b0;
	        end
	    end 
	end    

	// 这个always block是用来生成读响应的。
	always @( posedge clk )
	begin
	  if ( rst == 1'b1 )
	    begin
	      axi_rvalid <= 0;
	      axi_rresp  <= 0;
	    end 
	  else
	    begin    
	      if (axi_arready && S_AXI_ARVALID && ~axi_rvalid)
	        begin
	          // Valid read data is available at the read data bus
	          axi_rvalid <= 1'b1;
	          axi_rresp  <= 2'b0; // 'OKAY' response
	        end   
	      else if (axi_rvalid && S_AXI_RREADY) 
	        begin
	          // Read data is accepted by the master
	          axi_rvalid <= 1'b0;
	        end                
	    end
	end    

	//从从属寄存器(slv_reg0到slv_reg3)中读取数据。
	assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
	always @(*)
	begin
	      // Address decoding for reading registers
	      case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
	        2'h0   : reg_data_out <= slv_reg0;
	        2'h1   : reg_data_out <= slv_reg1;
	        2'h2   : reg_data_out <= slv_reg2;
	        2'h3   : reg_data_out <= slv_reg3;
	        default : reg_data_out <= 0;
	      endcase
	end
	
	// 这个always block是用来生成读数据的。
	always @( posedge clk )
	begin
	  if ( rst == 1'b1 )
	    begin
	      axi_rdata  <= 0;
	    end 
	  else
	    begin    
	      if (slv_reg_rden)
	        begin
	          axi_rdata <= reg_data_out;     // register read data
	        end   
	    end
	end    

	// User logic starts
	// 这个always block是用来复制寄存器数据的。
	//无论何时,reg_0, reg_1, reg_2和reg_3都将存储slv_reg0, slv_reg1, slv_reg2和slv_reg3
	// Add user logic here
	always@(*)begin
		reg_0<=slv_reg0;
		reg_1<=slv_reg1;
		reg_2<=slv_reg2;
		reg_3<=slv_reg3;
	end
	// User logic ends

	endmodule

这部分难度不高,主要是用来输出控制axilite总线的读、写、回应信号等,具体如何输入输出还得和其他模块联系起来看。

Axi lite(23条消息) AXI4_lite协议详解_theboynoName的博客-CSDN博客

generate_ctrl_signal

代码

module generate_ctrl_signal
(
	input clk,  // 时钟信号
	input rst,  // 复位信号
	
	input recv_enable,  // 接收使能信号
	input send_enable,  // 发送使能信号
	input conv_start,   // 转换开始信号
	
	input recv_done,    // 接收完成信号
	input send_done,    // 发送完成信号
	input conv_done,    // 转换完成信号
	
	output recv_running, // 接收正在运行信号
	output send_running, // 发送正在运行信号
	output conv_shutdown,// 转换关闭信号
	
	input task_valid,   // 任务有效信号
	output ap_done      // 任务完成信号
);

	wire [2:0] task_ctrl_line; // 任务控制线,由接收使能,发送使能,转换开始信号组成
	reg [2:0] task_reg;        // 存储任务控制线的寄存器
	assign task_ctrl_line={recv_enable,send_enable,conv_start};

	always@(posedge clk) begin
		if(rst) begin
			task_reg<=0; // 复位时,任务寄存器清零
		end else begin
			if(task_valid) begin
				task_reg<=task_ctrl_line; // 任务有效时,任务寄存器获取任务控制线的值
			end
		end
	end

	reg task_done; // 任务完成寄存器
	always@(posedge clk) begin
		case(task_reg)
			3'b100: task_done<=recv_done;   // 接收任务完成
			3'b010: task_done<=send_done;   // 发送任务完成
			3'b001: task_done<=conv_done;   // 转换任务完成
			3'b101: task_done<=recv_done;   // 接收任务完成
			default: task_done<=0;          // 默认情况,任务未完成
		endcase
	end
	
	reg ap_done_reg;  // 任务完成信号的寄存器
	assign ap_done=ap_done_reg;
	always@(posedge clk) begin
		if(rst||task_valid) begin
			ap_done_reg<=1'b0;  // 复位或者任务有效时,任务完成寄存器清零,表示开始任务
		end else begin
			if(task_done) begin
				ap_done_reg<=1'b1; // 任意任务完成时,任务完成寄存器置1
			end
		end
	end
	
	com_ctrl_task
	#(.POLARITY(1'b1)) u_com_ctrl_task_recv
	(
		.clk(clk),.rst(rst),
		.start_signal(recv_enable),
		.done_signal(recv_done),
		.running(recv_running)
	);
	
	com_ctrl_task
	#(.POLARITY(1'b0)) u_com_ctrl_task_conv
	(
		.clk(clk),.rst(rst),
		.start_signal(conv_start),
		.done_signal(conv_done),
		.running(conv_shutdown)
	);
	
	com_ctrl_task
	#(.POLARITY(1'b1)) u_com_ctrl_task_send
	(
		.clk(clk),.rst(rst),
		.start_signal(send_enable),
		.done_signal(send_done),
		.running(send_running)
	);

endmodule

该部分采用状态机设计,调用com ctrl task模块,根据模块输出的任务状态,输出任务运行/完成信号。

引用模块 com_ctrl_task

module com_ctrl_task
#(
	parameter POLARITY=1'b1 // 控制任务运行状态的极性,根据这个参数,running 信号可以在任务运行时是高电平还是低电平
)
(
	input clk,             // 时钟信号
	input rst,             // 复位信号
	
	input start_signal,    // 开始信号,表示一个新任务开始
	input done_signal,     // 完成信号,表示任务完成	
	
	output reg running     // 输出的运行状态,表示任务是否正在运行
);
	
	localparam IDLE		=	2'b00; // 空闲状态,没有任务正在运行
	localparam RUNNING	=	2'b01; // 运行状态,有任务正在运行
	localparam PENDING	=	2'b10; // 挂起状态,任务已完成,但未返回到空闲状态
	reg [1:0] curr_state;         // 当前状态寄存器,存储当前的状态
	reg [1:0] next_state;         // 下一个状态寄存器,存储即将转移到的状态

	// 时钟上升沿,如果复位信号高,当前状态设置为空闲,否则,当前状态更新为下一个状态
	always@(posedge clk) begin
		if(rst) begin
			curr_state<=IDLE;
		end else begin
			curr_state<=next_state;
		end
	end
	
	// 状态转移逻辑,根据当前状态和输入信号,确定下一个状态
	always@(*) begin
		case(curr_state)
			IDLE:   // 当前是空闲状态,如果有开始信号,下一个状态是运行状态,否则还是空闲状态
				if(start_signal) begin
					next_state<=RUNNING;
				end else begin
					next_state<=IDLE;
				end
			RUNNING: // 当前是运行状态,如果有完成信号,下一个状态是挂起状态,否则还是运行状态
				if(done_signal) begin
					next_state<=PENDING;
				end else begin
					next_state<=RUNNING;
				end
			PENDING: // 当前是挂起状态,无论有没有输入信号,下一个状态都是空闲状态
				next_state<=IDLE;
			default: // 如果当前状态是其他状态,就把下一个状态设置为空闲状态
				next_state<=IDLE;
		endcase
	end
	
	// 时钟上升沿,如果复位信号高,或者当前状态不是运行状态,running 信号设置为~POLARITY(表示任务不在运行),否则设置为POLARITY(表示任务在运行)
	always@(posedge clk) begin
		if(rst) begin
			running<=~POLARITY;
		end else begin
			if(curr_state==RUNNING) begin
				running<=POLARITY;
			end else begin
				running<=~POLARITY;
			end
		end
	end
endmodule

该模块采用状态机设计,负责输出发送、接收、转换的任务状态。 

疑问

    
	reg task_done; // 任务完成寄存器
	always@(posedge clk) begin
		case(task_reg)
			3'b100: task_done<=recv_done;   // 接收任务完成
			3'b010: task_done<=send_done;   // 发送任务完成
			3'b001: task_done<=conv_done;   // 转换任务完成
			3'b101: task_done<=recv_done;   // 接收任务完成    101表示接收使能和转换开始
			default: task_done<=0;          // 默认情况,任务未完成
		endcase
	end

 为什么当task_reg 101时候也是将recv_done输入到task_done中去?

	com_ctrl_task
	#(.POLARITY(1'b1)) u_com_ctrl_task_recv
	(
		.clk(clk),.rst(rst),
		.start_signal(recv_enable),
		.done_signal(recv_done),
		.running(recv_running)
	);
	
	com_ctrl_task
	#(.POLARITY(1'b0)) u_com_ctrl_task_conv
	(
		.clk(clk),.rst(rst),
		.start_signal(conv_start),
		.done_signal(conv_done),
		.running(conv_shutdown)
	);
	
	com_ctrl_task
	#(.POLARITY(1'b1)) u_com_ctrl_task_send
	(
		.clk(clk),.rst(rst),
		.start_signal(send_enable),
		.done_signal(send_done),
		.running(send_running)
	);

需注意,接收、发送的正在运行running信号是高电平有效,高电平表示正在运行,而转换关闭shutdown信号则是低电平有效,低电平表示转换关闭。

interface_axis_slave

代码

module interface_axis_slave
#(
	parameter ADDR_BIT=16  // 写地址位数
)
(
	input clk,              // 时钟信号
	input rst,              // 复位信号
	
	input recv_enable,      // 接收使能信号
	output recv_done,       // 接收完成信号
	
	output s_axis_tready,   // AXI Stream接收器就绪信号
	input [63:0] s_axis_tdata, // AXI Stream数据输入信号
	input s_axis_tlast,         // AXI Stream最后一位数据标志
	input s_axis_tvalid,        // AXI Stream有效数据标志
	
	output reg [ADDR_BIT-1:0] write_addr, // 写地址信号
	output [63:0] write_data,  // 写数据信号
	output write_enable        // 写使能信号
);

	localparam IDLE		=	2'b00;  // 空闲状态
	localparam SET		=	2'b01;  // 设定状态
	localparam FINISH	=	2'b11;  // 完成状态
	
	reg [1:0] curr_state;   // 当前状态寄存器
	reg [1:0] next_state;   // 下一个状态寄存器
	
	// 状态转移逻辑
	always@(posedge clk) begin
		if(rst) begin
			curr_state<=IDLE;  // 复位时进入空闲状态
		end else begin
			curr_state<=next_state;  // 非复位时转移至下一个状态
		end
	end

	// 状态设定逻辑
	always@(*) begin
		case(curr_state)
			IDLE:  // 当前状态为“空闲”
				if(recv_enable) begin
					next_state<=SET;  // 当接收使能时,进入“设定”状态
				end else begin
					next_state<=IDLE; // 否则仍保持在“空闲”状态
				end
			SET:   // 当前状态为“设定”
				if(s_axis_tlast) begin
					next_state<=FINISH;  // 当AXI Stream最后一位数据标志为1时,进入“完成”状态
				end else begin
					next_state<=SET; // 否则仍保持在“设定”状态
				end
			FINISH: // 当前状态为“完成”,下一个状态为“空闲”
				next_state<=IDLE;
			default: // 默认状态为“空闲”
				next_state<=IDLE;
		endcase
	end
	
	wire clr;  // 清除信号
	
	// 信号绑定
	assign recv_done     =(curr_state==FINISH);  // 当前状态为“完成”时,接收完成信号为1
	assign clr           =rst||recv_done;        // 复位信号或接收完成信号为1时,清除信号为1
	assign s_axis_tready =(curr_state!=IDLE);    // 当前状态非“空闲”时,AXI Stream接收器就绪信号为1
	assign write_enable  =s_axis_tready&&s_axis_tvalid;  // AXI Stream接收器就绪且有有效数据时,写使能信号为1
	assign write_data    =s_axis_tdata;  // 写数据信号为AXI Stream数据信号

	// 写地址逻辑
	always@(posedge clk) begin
		if(clr) begin
			write_addr<=0;  // 当清除信号为1时,写地址信号为0
		end else begin
			if(write_enable) begin
				write_addr<=write_addr+1;  // 当写使能信号为1时,写地址信号自增
			end else begin
				write_addr<=write_addr;    // 否则,写地址信号不变
			end
		end
	end
endmodule

 疑问

	// 写地址逻辑
	always@(posedge clk) begin
		if(clr) begin
			write_addr<=0;  // 当清除信号为1时,写地址信号为0
		end else begin
			if(write_enable) begin
				write_addr<=write_addr+1;  // 当写使能信号为1时,写地址信号自增
			end else begin
				write_addr<=write_addr;    // 否则,写地址信号不变
			end
		end
	end

为什么写地址每次都从0开始?

当写使能为1时,写地址信号每个时钟周期增加1,猜测是每个时钟周期写入一个数据,下一个数据地址+1,具体功能还得到后续代码读完后补充。

interface_axis_master

代码

module interface_axis_master
#(
	parameter ADDR_BIT=16
)
(
	input clk,                  // 时钟信号
	input rst,                  // 复位信号
	
	input send_enable,          // 发送使能信号
	output send_done,           // 发送完成信号

	output m_axis_tvalid,       // 主设备发送数据有效信号
	output [63:0] m_axis_tdata, // 主设备发送的数据
	output m_axis_tlast,        // 主设备发送的最后一个数据标志
	input m_axis_tready,        // 主设备发送数据就绪信号

	input [ADDR_BIT-1:0] addr_end,   // 地址范围的结束地址
	input [ADDR_BIT-1:0] addr_start, // 地址范围的起始地址
	
	output [ADDR_BIT-1:0] read_addr, // 读取数据的地址
	input [63:0] read_data           // 读取的数据
);
	localparam IDLE    = 1'b0;    // 空闲状态
	localparam RUNNING = 1'b1;    // 运行状态
	
	reg curr_state;               // 当前状态
	reg next_state;               // 下一个状态

	reg [ADDR_BIT-1:0] addr;      // 存储地址值

	wire last;                    // 最后一个数据标志
	reg last_d;                   // 最后一个数据标志的延迟寄存器
	assign last = (addr == (addr_end - addr_start - 1));  // 判断是否为最后一个数据
	always @(posedge clk) begin
		last_d <= last;  // 延迟一个时钟周期
	end
	
	wire valid;                   // 数据有效标志
	assign valid = (addr != 0);   // 当地址不为零时表示数据有效
	assign send_done = last_d;    // 发送完成信号与最后一个数据标志相关
	assign m_axis_tlast = last_d; // 主设备发送的最后一个数据标志
	assign m_axis_tvalid = valid; // 主设备发送数据有效信号
	assign m_axis_tdata = read_data; // 主设备发送的数据
	assign read_addr = addr + addr_start; // 读取数据的地址
	
	always @(posedge clk) begin
		if (rst) begin
			curr_state <= IDLE;  // 复位时进入空闲状态
		end else begin
			curr_state <= next_state;  // 根据下一个状态更新当前状态
		end
	end
	
	always @(*) begin
		case (curr_state)
			IDLE:
				if (send_enable) begin
					next_state <= RUNNING;  // 如果发送使能信号有效,则进入运行状态
				end else begin
					next_state <= IDLE;     // 否则保持空闲状态
				end
			RUNNING:
				if (last) begin
					next_state <= IDLE;     // 如果是最后一个数据,则返回空闲状态
				end else begin
					next_state <= RUNNING;  // 否则继续保持运行状态
				end
			default:
				next_state <= IDLE;        // 默认情况下返回空闲状态
		endcase
	end
	
	always @(posedge clk) begin
		if (curr_state == IDLE) begin
			addr <= 0;                // 在空闲状态时将地址归零
		end else begin
			if (m_axis_tready) begin
				addr <= addr + 1;     // 如果主设备发送数据就绪,则地址递增
			end else begin
				addr <= addr;         // 否则地址保持不变
			end
		end
	end
endmodule

疑问

与slave不同的是,master输入包括地址范围的开始结束地址,最后输出的读地址位置是addr+addr_start。

interface_axis_slave和interface_axis_master都只是作为中间传输作用,负责生成读写的数据、地址、使能信号,并给对方反馈,进行数据储存和读取操作包含在其他代码中:(待补充)

axis_buf_sel

代码

module axis_buf_sel
#(
	parameter DMA_ADDR_BIT =18 // 定义DMA地址的位宽
)
(
	input	[1:0]				axis_buf_sel, // 输入的缓冲区选择信号

	// 写入操作的相关输入
	input	[DMA_ADDR_BIT-1:0]	write_addr, // 写入的地址
	input	[63:0]				write_data, // 写入的数据
	input						write_enable, // 写入使能信号

	// 对应ifm缓冲区的写入接口
	output	[DMA_ADDR_BIT-1:0]	write_addr_ifm, // ifm的写入地址
	output	[63:0]				write_data_ifm, // ifm的写入数据
	output						write_enable_ifm, // ifm的写入使能信号

	// 对应weight缓冲区的写入接口
	output	[DMA_ADDR_BIT-1:0]	write_addr_weight, // weight的写入地址
	output	[63:0]				write_data_weight, // weight的写入数据
	output						write_enable_weight, // weight的写入使能信号

	// 对应bias缓冲区的写入接口
	output	[DMA_ADDR_BIT-1:0]	write_addr_bias, // bias的写入地址
	output	[63:0]				write_data_bias, // bias的写入数据
	output						write_enable_bias, // bias的写入使能信号

	// 对应leakyrelu缓冲区的写入接口
	output	[DMA_ADDR_BIT-1:0]	write_addr_leakyrelu, // leakyrelu的写入地址
	output	[63:0]				write_data_leakyrelu, // leakyrelu的写入数据
	output						write_enable_leakyrelu // leakyrelu的写入使能信号
);
	
	// 判断输入选择信号,确定要操作的缓冲区
	wire buf_sel_ifm            =   (axis_buf_sel==2'b00); // 若选择信号为00,则选择ifm
	wire buf_sel_weight         =   (axis_buf_sel==2'b01); // 若选择信号为01,则选择weight
	wire buf_sel_bias           =   (axis_buf_sel==2'b11); // 若选择信号为11,则选择bias
	wire buf_sel_leakyrelu      =   (axis_buf_sel==2'b10); // 若选择信号为10,则选择leakyrelu
	
	// 根据选择的缓冲区,决定各输出信号的值
	// 若选择了某一缓冲区,则对应的地址和数据输出为输入的地址和数据,使能信号为输入的使能信号;否则,这些输出全为0
	assign write_addr_ifm         =   (buf_sel_ifm)?write_addr:0;
	assign write_data_ifm         =   (buf_sel_ifm)?write_data:0;
	assign write_enable_ifm       =   (buf_sel_ifm)?write_enable:0;
	
	assign write_addr_bias        =   (buf_sel_bias)?write_addr:0;
	assign write_data_bias        =   (buf_sel_bias)?write_data:0;
	assign write_enable_bias      =   (buf_sel_bias)?write_enable:0;
	
	assign write_addr_weight      =   (buf_sel_weight)?write_addr:0;
	assign write_data_weight      =   (buf_sel_weight)?write_data:0;
	assign write_enable_weight    =   (buf_sel_weight)?write_enable:0;
	
	assign write_addr_leakyrelu   =   (buf_sel_leakyrelu)?write_addr:0;
	assign write_data_leakyrelu   =   (buf_sel_leakyrelu)?write_data:0;
	assign write_enable_leakyrelu =   (buf_sel_leakyrelu)?write_enable:0;

endmodule

这部分代码只是简单的根据输入的缓冲区选择信号,用于在FPGA中控制数据的写入到不同的缓冲区。这个模块通过输入的选择信号(axis_buf_sel)来决定将数据写入到哪一个缓冲区(ifm, weight, bias, 或 leakyrelu)。

global_data_beat(头疼!看不懂md

代码

module global_data_beat                             //处理全局数据的节拍控制,时序控制
#(
	parameter ADDR_BIT=12
)
(
	input clk,
	input shutdown,
	
	input [ADDR_BIT-1:0] conv_addr_len,
	input [ADDR_BIT-1:0] pool_addr_len,
	input [8:0] conv_col,
	input [8:0] conv_row,
	input pool_stride_sel,							//池化步长选择 1/2
	
	output [ADDR_BIT-1:0] ifmbuf_bram_addr_read,	//用于从BRAM读取IFM缓冲器的地址。
	
	output acc_read_en,								//用于启用读取和写入累加器。
	output acc_write_en,
	output [ADDR_BIT-1:0] acc_read_addr,			//输出信号指定从累加器读取和写入的地址。
	output [ADDR_BIT-1:0] acc_write_addr,
	output acc_curr_data_zero,						//表示当前读取的累加器数据是否为零。
	
	output [ADDR_BIT-1:0] ofm_after_quant_addr,		//表示量化后的OFM的地址
	output ofm_after_quant_valid,					//表示量化后的OFM的数据是否有效
	output ofm_after_quant_done,					//表示量化后的OFM的数据是否已经全部生成
	
	output [ADDR_BIT-1:0] ofm_after_pool_addr,		//池化后的OFM数据
	output ofm_after_pool_valid,	
	output ofm_after_pool_zero,
	output ofm_after_pool_done
);
	// global addr and global valid, zero
	wire [8:0] conv_col_minus_1;				    //这些信号主要用于计算全局地址和全局有效性以及零值。
	wire [8:0] conv_col_minus_2;					//通过对输入信号 conv_col 和 conv_row 进行加减操作,
	wire [8:0] conv_row_minus_1;					//生成一些中间结果,用于后续逻辑中的地址计算和有效性判断。
	wire [8:0] conv_col_add_1;
	wire [15:0] conv_col_mult_2;
	wire [15:0] addr_len_add_conv_col_add_1;
	
	assign conv_col_minus_1=conv_col-1'b1;
	assign conv_col_minus_2=conv_col-2'b10;
	assign conv_row_minus_1=conv_row-1'b1;
	assign conv_col_add_1=conv_col+1'b1;
	assign conv_col_mult_2=(conv_col<<1);
	assign addr_len_add_conv_col_add_1=conv_addr_len+conv_col+1;
	
	reg [15:0] addr_cnt;				//地址计数
	reg [8:0] col_cnt;					//列计数
	always@(posedge clk) begin			//卷积计数器,当转化任务正在运行时,
		if(shutdown) begin				//地址计数器和列计数器每时钟周期+1
			addr_cnt<=0;
			col_cnt<=0;
		end else begin
			if(col_cnt==conv_col_minus_1) begin		
				addr_cnt<=addr_cnt+1;
				col_cnt<=0;
			end else begin
				addr_cnt<=addr_cnt+1;
				col_cnt<=col_cnt+1;
			end
		end
	end
	
	reg global_zero;
	reg global_valid;
	always@(posedge clk) begin
		if(col_cnt<2 || (addr_cnt<conv_col_mult_2 || addr_cnt>=conv_addr_len)) begin
			global_zero<=1'b1;
		end else begin
			global_zero<=1'b0;
		end
	end
	always@(posedge clk) begin
		if(addr_cnt>=conv_col_add_1 && addr_cnt<addr_len_add_conv_col_add_1) begin
			global_valid<=1'b1;
		end else begin
			global_valid<=1'b0;
		end
	end
	
	/* Latency Summary
	 * Linebuffer	1
	 * cal_mult_dsp	4
	 * adder_pre	2
	 * adder_post	2
	 * acc			1
	 * quant		7 */
	
	// delay valid	
	wire valid_delay_9;
	reg valid_delay_10;
	reg valid_delay_11;
	
	wire valid_delay_18;
	
	com_shift_reg
	#(.DEPTH(9),.WIDTH(1),.SRL_STYLE_VAL("srl_reg"))
	u_com_shift_reg_2
	(
		.clk(clk),
		.si(global_valid),
		.so(valid_delay_9)
	);
	com_shift_reg
	#(.DEPTH(18),.WIDTH(1),.SRL_STYLE_VAL("srl_reg"))
	u_com_shift_reg_3
	(
		.clk(clk),
		.si(global_valid),
		.so(valid_delay_18)
	);
	always@(posedge clk) begin
		valid_delay_10<=valid_delay_9;
		valid_delay_11<=valid_delay_10;
	end
	assign acc_read_en=valid_delay_9;
	assign acc_write_en=valid_delay_11;
	assign ofm_after_quant_valid=valid_delay_18;
	
	// delay addr
	reg [15:0] base_out_addr;
	wire [15:0] base_out_addr_delay_2;
	wire [15:0] base_out_addr_delay_9;
	always@(posedge clk) begin
		if(shutdown) begin
			base_out_addr<=0;
		end else begin
			if(valid_delay_9) begin
				base_out_addr<=base_out_addr+1;
			end
		end
	end
	com_shift_reg
	#(.DEPTH(2),.WIDTH(ADDR_BIT),.SRL_STYLE_VAL("srl_reg"))
	u_com_shift_reg_4
	(
		.clk(clk),
		.si(base_out_addr),
		.so(base_out_addr_delay_2)
	);
	com_shift_reg
	#(.DEPTH(7),.WIDTH(ADDR_BIT),.SRL_STYLE_VAL("srl_reg"))
	u_com_shift_reg_5
	(
		.clk(clk),
		.si(base_out_addr_delay_2),
		.so(base_out_addr_delay_9)
	);
	assign ifmbuf_bram_addr_read=addr_cnt;
	assign acc_read_addr=base_out_addr;
	assign acc_write_addr=base_out_addr_delay_2;
	assign ofm_after_quant_addr=base_out_addr_delay_9;
	
	// delay zero
	wire zero_delay_10;
	com_shift_reg
	#(.DEPTH(10),.WIDTH(1),.SRL_STYLE_VAL("srl_reg"))
	u_com_shift_reg_6
	(
		.clk(clk),
		.si(global_zero),
		.so(zero_delay_10)
	);
	assign acc_curr_data_zero=zero_delay_10;
	
	com_negedge_detect u_com_negedge_detect_quant_done
	(.clk(clk),.signal(ofm_after_quant_valid),.pulse(ofm_after_quant_done));
	

	// pool data beat-------------------------------------------------------
	wire [15:0] pool_addr_len_stride_1; // 池化地址长度(步长为1)
	wire [15:0] pool_addr_len_stride_2; // 池化地址长度(步长为2)
	assign pool_addr_len_stride_1={{4'b0000},conv_addr_len}; // 低12位为conv_addr_len,高4位为0
	assign pool_addr_len_stride_2={{4'b0000},pool_addr_len}; // 低12位为pool_addr_len,高4位为0
	
	wire pool_valid_in; // 池化输入有效信号
	assign pool_valid_in=ofm_after_quant_valid; // 将ofm_after_quant_valid信号赋给pool_valid_in
	
	// 定义状态机的两个状态
	localparam IDLE=1'b0; 
	localparam RUNNING=1'b1;
	reg curr_state; // 当前状态
	reg next_state; // 下一个状态
	wire pool_running;
	assign pool_idle=(curr_state==IDLE); // 如果当前状态为IDLE,则pool_idle为true
	
	// 状态机的状态更新
	always@(posedge clk) begin
		if(shutdown) begin // 如果shutdown为true,则状态机进入IDLE状态
			curr_state<=IDLE;
		end else begin
			curr_state<=next_state; // 否则,状态机进入下一个状态
		end
	end
	
	// 状态转移逻辑
	always@(*) begin
		case(curr_state)
			IDLE: // 当前状态为IDLE时
				if(pool_valid_in) begin // 如果输入有效,则进入RUNNING状态
					next_state<=RUNNING;
				end else begin
					next_state<=IDLE; // 否则,保持IDLE状态
				end
			RUNNING: // 当前状态为RUNNING时
				if(ofm_after_pool_done) begin // 如果池化操作完成,则进入IDLE状态
					next_state<=IDLE;
				end else begin
					next_state<=RUNNING; // 否则,保持RUNNING状态
				end
			default: // 其他情况,进入IDLE状态
				next_state<=IDLE;
		endcase
	end
	
	reg [8:0] pool_col_addr; // 池化列地址
	reg [8:0] pool_row_addr; // 池化行地址
	wire pool_cond; 
	assign pool_cond=(pool_col_addr==conv_col_minus_1); // pool_cond为true表示已到达列的最后位置
	
	// 列地址的更新
	always@(posedge clk) begin
		if(shutdown||pool_idle||pool_cond) begin // 如果shutdown为true,或pool_idle为true,或已到达列的最后位置,则列地址重置为0
			pool_col_addr<=0;
		end else begin
			pool_col_addr<=pool_col_addr+1; // 否则,列地址增1
		end
	end
	
	// 行地址的更新
	always@(posedge clk) begin
		if(shutdown||pool_idle) begin // 如果shutdown为true,或pool_idle为true,则行地址重置为0
			pool_row_addr<=0;
		end else begin
			if(pool_cond) begin // 如果已到达列的最后位置,则行地址增1
				pool_row_addr<=pool_row_addr+1;
			end else begin
				pool_row_addr<=pool_row_addr; // 否则,行地址保持不变
			end
		end
	end
	
	
	// global addr and global valid, zero, stride=2----------------------------------------
	reg pool_col_valid_s2; // 列地址有效的标志,步长为2
	reg pool_row_valid_s2; // 行地址有效的标志,步长为2
	
	// 更新列地址有效的标志
	always@(posedge clk) begin
		if(shutdown||pool_idle) begin // 如果处于关闭状态或池化处于空闲状态
			pool_col_valid_s2<=1; // 列地址有效标志为1
		end else begin
			pool_col_valid_s2<=~pool_col_valid_s2; // 否则,列地址有效标志取反
		end
	end
	
	// 更新行地址有效的标志
	always@(posedge clk) begin
		if(shutdown||pool_idle) begin // 如果处于关闭状态或池化处于空闲状态
			pool_row_valid_s2<=1; // 行地址有效标志为1
		end else begin
			if(pool_cond) begin // 如果已经到达列的末尾
				pool_row_valid_s2<=~pool_row_valid_s2; // 行地址有效标志取反
			end
		end
	end
	
	wire pool_col_zero_s2; // 列地址为0的标志,步长为2
	wire pool_row_zero_s2; // 行地址为0的标志,步长为2
	// 列地址为0或者到达最后一列,pool_col_zero_s2为1
	assign pool_col_zero_s2=((pool_col_addr==0)||(pool_col_addr==conv_col_minus_1));
	// 行地址为0或者到达最后一行,pool_row_zero_s2为1
	assign pool_row_zero_s2=((pool_row_addr==0)||(pool_row_addr==conv_row_minus_1));
	
	wire gen_pool_valid_s2; // 生成的池化有效信号,步长为2
	wire gen_pool_zero_s2; // 生成的池化0值信号,步长为2
	// 生成池化有效信号,当列地址有效或者列地址为0,且行地址有效或者行地址为0,且池化非空闲状态时,gen_pool_valid_s2为1
	assign gen_pool_valid_s2=(pool_col_valid_s2||pool_col_zero_s2)&&(pool_row_valid_s2||pool_row_zero_s2)&&(!pool_idle);
	// 生成池化0值信号,当列地址为0或者行地址为0时,gen_pool_zero_s2为1
	assign gen_pool_zero_s2=pool_col_zero_s2||pool_row_zero_s2;
	
	reg pool_valid_d1_s2; // 延迟1个周期的池化有效信号,步长为2
	reg pool_valid_d2_s2; // 延迟2个周期的池化有效信号,步长为2
	reg pool_valid_d3_s2; // 延迟3个周期的池化有效信号,步长为2
	reg pool_valid_d4_s2; // 延迟4个周期的池化有效信号,步长为2
	// 更新延迟的池化有效信号
	always@(posedge clk) begin
		pool_valid_d1_s2<=gen_pool_valid_s2;
		pool_valid_d2_s2<=pool_valid_d1_s2;
		pool_valid_d3_s2<=pool_valid_d2_s2;
		pool_valid_d4_s2<=pool_valid_d3_s2;
	end
	
	reg pool_zero_d1_s2; // 延迟1个周期的池化0值信号,步长为2
	reg pool_zero_d2_s2; // 延迟2个周期的池化0值信号,步长为2
	reg pool_zero_d3_s2; // 延迟3个周期的池化0值信号,步长为2
	reg pool_zero_d4_s2; // 延迟4个周期的池化0值信号,步长为2
	// 更新延迟的池化0值信号
	always@(posedge clk) begin
		pool_zero_d1_s2<=gen_pool_zero_s2;
		pool_zero_d2_s2<=pool_zero_d1_s2;
		pool_zero_d3_s2<=pool_zero_d2_s2;
		pool_zero_d4_s2<=pool_zero_d3_s2;
	end
	
	reg [15:0] pool_addr_s2; // 池化地址,步长为2
	wire pool_last_s2; // 池化的最后一个地址的标志,步长为2
	assign pool_last_s2=(pool_addr_s2==pool_addr_len_stride_2-1); // 当池化地址等于最后一个地址时,pool_last_s2为1
	
	// 更新池化地址
	always@(posedge clk) begin
		if(shutdown||pool_idle) begin // 如果处于关闭状态或池化处于空闲状态
			pool_addr_s2<=0; // 池化地址重置为0
		end else begin
			if(pool_valid_d3_s2) begin // 如果延迟3个周期的池化有效信号为1
				pool_addr_s2<=pool_addr_s2+1; // 池化地址增1
			end else begin
				pool_addr_s2<=pool_addr_s2; // 否则,池化地址保持不变
			end	
		end
	end
	
	
	// global addr and global valid, zero, stride=1----------------------------------------
// 列地址为0的条件,步长为1
	wire pool_col_zero_s1;
	// 行地址为0的条件,步长为1
	wire pool_row_zero_s1;
	// 列地址为0或者列地址到达最后一列或者列地址到达倒数第二列,pool_col_zero_s1为1
	assign pool_col_zero_s1=((pool_col_addr==0)||(pool_col_addr==conv_col_minus_1)||(pool_col_addr==conv_col_minus_2));
	// 行地址为0或者行地址为1或者行地址到达最后一行或者行地址等于conv_row,pool_row_zero_s1为1
	assign pool_row_zero_s1=((pool_row_addr==0)||(pool_row_addr==1)||(pool_row_addr==conv_row_minus_1)||(pool_row_addr==conv_row));
	
	wire gen_pool_valid_s1; // 生成的池化有效信号,步长为1
	wire gen_pool_zero_s1; // 生成的池化0值信号,步长为1
	// 行地址大于等于1且小于等于conv_row且池化非空闲状态,gen_pool_valid_s1为1
	assign gen_pool_valid_s1=((pool_row_addr>=1)&&(pool_row_addr<=conv_row)&&(!pool_idle));
	// 列地址为0或者行地址为0,gen_pool_zero_s1为1
	assign gen_pool_zero_s1=pool_col_zero_s1||pool_row_zero_s1;
	
	reg pool_valid_d1_s1; // 延迟1个周期的池化有效信号,步长为1
	reg pool_valid_d2_s1; // 延迟2个周期的池化有效信号,步长为1
	reg pool_valid_d3_s1; // 延迟3个周期的池化有效信号,步长为1
	reg pool_valid_d4_s1; // 延迟4个周期的池化有效信号,步长为1
	// 更新延迟的池化有效信号
	always@(posedge clk) begin
		pool_valid_d1_s1<=gen_pool_valid_s1;
		pool_valid_d2_s1<=pool_valid_d1_s1;
		pool_valid_d3_s1<=pool_valid_d2_s1;
		pool_valid_d4_s1<=pool_valid_d3_s1;
	end
	
	reg pool_zero_d1_s1; // 延迟1个周期的池化0值信号,步长为1
	reg pool_zero_d2_s1; // 延迟2个周期的池化0值信号,步长为1
	reg pool_zero_d3_s1; // 延迟3个周期的池化0值信号,步长为1
	reg pool_zero_d4_s1; // 延迟4个周期的池化0值信号,步长为1
	// 更新延迟的池化0值信号
	always@(posedge clk) begin
		pool_zero_d1_s1<=gen_pool_zero_s1;
		pool_zero_d2_s1<=pool_zero_d1_s1;
		pool_zero_d3_s1<=pool_zero_d2_s1;
		pool_zero_d4_s1<=pool_zero_d3_s1;
	end
	
	reg [15:0] pool_addr_s1; // 池化地址,步长为1
	wire pool_last_s1; // 池化的最后一个地址的标志,步长为1
	assign pool_last_s1=(pool_addr_s1==pool_addr_len_stride_1-1); // 当池化地址等于最后一个地址时,pool_last_s1为1
	
	// 更新池化地址
	always@(posedge clk) begin
		if(shutdown||pool_idle) begin // 如果处于关闭状态或池化处于空闲状态
			pool_addr_s1<=0; // 池化地址重置为0
		end else begin
			if(pool_valid_d4_s1) begin // 如果延迟4个周期的池化有效信号为1
				pool_addr_s1<=pool_addr_s1+1; // 池化地址增1
			end else begin
				pool_addr_s1<=pool_addr_s1; // 否则,池化地址保持不变
			end
		end
	end
	
	
	assign ofm_after_pool_addr=(pool_stride_sel==1'b1)?pool_addr_s1:pool_addr_s2;
	assign ofm_after_pool_valid=(pool_stride_sel==1'b1)?pool_valid_d4_s1:pool_valid_d3_s2;
	assign ofm_after_pool_zero=(pool_stride_sel==1'b1)?pool_zero_d3_s1:pool_zero_d2_s2;
	assign ofm_after_pool_done=(pool_stride_sel==1'b1)?pool_last_s1:pool_last_s2;
endmodule

引用模块

module com_shift_reg
#(
	parameter DEPTH=30, // 参数DEPTH定义了移位寄存器的深度,即有多少个寄存器级联
	parameter WIDTH=8,  // 参数WIDTH定义了每个寄存器的宽度,即每个寄存器可以存储多少比特的信息
	parameter SRL_STYLE_VAL="reg_srl_reg" // 一种指示生成硬件资源类型的参数,这里定义了移位寄存器的实现方式
)
(
	input clk,  // 时钟信号输入
	input [WIDTH-1:0] si,  // 宽度为WIDTH的串行输入
	output [WIDTH-1:0] so  // 宽度为WIDTH的串行输出
);

	(* srl_style=SRL_STYLE_VAL*) // 这个属性用于指定使用哪种类型的移位寄存器
	reg [WIDTH-1:0] sreg [0:DEPTH];  // 定义一个深度为DEPTH,宽度为WIDTH的寄存器数组,用于存储输入的数据
	
	integer t;
	initial begin
		// 在仿真开始时,将所有的移位寄存器初始化为0
		for(t=0;t<=DEPTH;t=t+1)
			sreg[t]=0;
	end
	
	// 将最后一个寄存器的值赋给输出so,即取出数据
	assign so=sreg[DEPTH];

	// 处理输入si,将si的值赋给第一个寄存器
	always@(*) begin
		sreg[0]=si;
	end

	// 针对除第一个寄存器外的所有寄存器,每个时钟上升沿,都将前一个寄存器的值赋给当前寄存器,实现数据的移动
	genvar i;
	generate 
		for(i=1;i<=DEPTH;i=i+1)
		begin
			always@(posedge clk)
			begin
					sreg[i]<=sreg[i-1];
			end
		end
	endgenerate
endmodule

这个模块的主要功能是接收串行输入数据,并在每个时钟周期将数据向后移动。移位寄存器的深度(DEPTH)和每个寄存器的宽度(WIDTH)可以进行配置,以适应不同的应用需求。最后,通过so输出。

其中使用了(* srl_style=SRL_STYLE_VAL*) // 这个属性用于指定使用哪种类型的移位寄存器,(23条消息) Vivado综合属性之SRL_STYLE_vivado 移位寄存器_努力不期待的博客-CSDN博客

 关于为什么采用该类型的移位寄存器我还不清楚。

reg-srl-reg第一和最后一级深度用FF(Flip Flop,触发器),其他用LUT。

Flip-Flop (FF) 和 Look-Up Table (LUT) 都是在数字电路设计中广泛使用的基础元件。

  1. Flip-Flop (触发器): Flip-Flop 是一种具有两个稳定状态的电路,可以用来存储一位 (bit) 的信息。Flip-Flops 在每个时钟周期会根据输入信号更新其状态,因此它们常被用作内存元素,在存储器,寄存器,计数器等许多数字系统中都能找到它们的身影。Flip-Flops 常用于存储或传递在特定时钟周期的数据。
  2. Look-Up Table (LUT): LUT 是一种在内部存储固定函数映射的电路元件,输入一组特定的值,就会输出相应的结果。在 FPGA (Field Programmable Gate Array) 中,LUT 是实现复杂逻辑功能的主要元素。FPGA设计者可以将所需的逻辑功能编程到 LUT 中,从而实现任意的逻辑功能。

在一个移位寄存器设计中,使用 Flip-Flop 作为第一级和最后一级,而其他级别使用 LUT,可能是为了优化性能。因为 Flip-Flop 的性能(如:延迟、吞吐量等)通常比 LUT 更优,而 LUT 则能提供更大的灵活性来实现复杂的逻辑功能。

第一级和最后一级的 Flip-Flop 可以提供稳定且快速的输入和输出,而中间级别的 LUT 则可以实现任意的数据处理逻辑。这样的结构可以在满足速度要求的同时,提供足够的灵活性来实现复杂的数据处理功能。

疑问

1.

output [ADDR_BIT-1:0] ifmbuf_bram_addr_read,//用于从BRAM读取IFM缓冲器的地址。

2. 

output acc_read_en,								//用于启用读取和写入累加器。
	output acc_write_en,
	output [ADDR_BIT-1:0] acc_read_addr,			//输出信号指定从累加器读取和写入的地址。
	output [ADDR_BIT-1:0] acc_write_addr,
	output acc_curr_data_zero,						//表示当前读取的累加器数据是否为零。

 这部分输入端口是用于卷积代码中acc_1*8模块的输入,应该是用于累加器,具体用途等待补充。

3. 

    reg global_zero;
	reg global_valid;
	always@(posedge clk) begin
		if(col_cnt<2 || (addr_cnt<conv_col_mult_2 || addr_cnt>=conv_addr_len)) begin
			global_zero<=1'b1;
		end else begin
			global_zero<=1'b0;
		end
	end
	always@(posedge clk) begin
		if(addr_cnt>=conv_col_add_1 && addr_cnt<addr_len_add_conv_col_add_1) begin
			global_valid<=1'b1;
		end else begin
			global_valid<=1'b0;
		end
	end

该部分输出的global_zero和valid,输出信号经过com_shift_reg模块延迟后,分别输出延迟信号acc_curr_data_zero与acc_read_en、acc_write_en、ofm_after_quant_valid。

global zero应该是用作让累加器每完成一次计算就归零。global valid控制累加器和ofm输出的使能信号。

    assign acc_curr_data_zero=zero_delay_10;

    assign acc_read_en=valid_delay_9;
	assign acc_write_en=valid_delay_11;
	assign ofm_after_quant_valid=valid_delay_18;

有效信号是使能信号的判断依据,其中延迟时钟周期数的不同,就是对acc读写以及ofm输出的时序控制。

至于为什么global zero与valid的判断依据是(col_cnt<2 || (addr_cnt<conv_col_mult_2 || addr_cnt>=conv_addr_len))与(addr_cnt>=conv_col_add_1 && addr_cnt<addr_len_add_conv_col_add_1)待后续看完代码再来补充。

4.太多了,之后这部分重新写

  • 6
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
Zynq是一种基于Xilinx的可编程逻辑器件(FPGA)和嵌入式处理器(ARM)的SoC。要在Zynq上实现Yolov3 Tiny人脸识别,你可以按照以下步骤进行: 1. 准备硬件:购买或设计一个带有Zynq芯片的开发板,例如Xilinx ZedBoard或Pynq-Z1。确保板载的摄像头可以提供适当的图像输入。 2. 安装开发环境:根据你选择的开发板,安装相应的开发工具和软件。对于ZedBoard,你可以使用Xilinx Vivado和Xilinx SDK。 3. 下载Yolov3 Tiny模型:从Darknet网站或其他源下载Yolov3 Tiny的权重文件和配置文件。这些文件描述了网络的结构和参数。 4. 转换模型:由于Zynq上的资源有限,你可能需要将Yolov3 Tiny模型转换为适合在FPGA实现的形式。这通常涉及到剪枝、量化和其他优化技术。你可以使用Xilinx的DNNDK(深度神经网络开发工具包)来进行这些操作。 5. 开发嵌入式应用程序:使用Xilinx SDK或其他嵌入式开发工具,编写一个应用程序来加载Yolov3 Tiny模型,并在Zynq上运行推理。该应用程序应该能够从摄像头获取图像,并将识别到的人脸信息显示出来。 6. 调试和优化:在Zynq上运行你的应用程序,并进行调试和优化,以确保人脸识别的准确性和性能。 请注意,实现Yolov3 Tiny人脸识别是一个复杂的任务,需要一定的硬件和软件开发经验。此外,由于Zynq资源有限,可能需要进一步优化和调整模型以适应硬件平台。建议你参考相关的文档、教程和示例代码,以获得更详细的指导。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值