目录
总体概览:编辑
卷积加速器部分代码:
视频接收与输出部分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) 都是在数字电路设计中广泛使用的基础元件。
- Flip-Flop (触发器): Flip-Flop 是一种具有两个稳定状态的电路,可以用来存储一位 (bit) 的信息。Flip-Flops 在每个时钟周期会根据输入信号更新其状态,因此它们常被用作内存元素,在存储器,寄存器,计数器等许多数字系统中都能找到它们的身影。Flip-Flops 常用于存储或传递在特定时钟周期的数据。
- 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)待后续看完代码再来补充。文章来源:https://www.toymoban.com/news/detail-714582.html
4.太多了,之后这部分重新写文章来源地址https://www.toymoban.com/news/detail-714582.html
到了这里,关于FPGA实现YOLOv3 tiny 代码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!