You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
verilog_stu/xdma/ch_3/axi_mpeg2encoder_wrapper_cn.sv

298 lines
17 KiB
Systemverilog

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 定义一个名为axi_mpeg2encoder_wrapper的模块包含AXI总线接口参数化的定义
module axi_mpeg2encoder_wrapper #(
parameter AXI_IDWIDTH = 4 // AXI总线ID宽度可配置参数
) (
// 输入时钟和复位信号
input wire rstn, // 异步复位信号,低电平有效
input wire clk, // 系统时钟信号
// AXI-MM 写地址接口 (AW) -----------------------------------------------
output wire s_axi_awready, // 写地址通道准备好信号,向主机表明可以接受新地址
input wire s_axi_awvalid, // 写地址通道有效信号,主机表明当前地址有效
input wire [ 63:0] s_axi_awaddr, // 写地址
input wire [ 7:0] s_axi_awlen, // 写突发长度
input wire [AXI_IDWIDTH-1:0] s_axi_awid, // 写事务ID
// AXI-MM 写数据接口 (W) -----------------------------------------------
output wire s_axi_wready, // 写数据通道准备好信号
input wire s_axi_wvalid, // 写数据通道有效信号
input wire s_axi_wlast, // 写数据突发的最后一个数据
input wire [ 63:0] s_axi_wdata, // 写数据
// AXI-MM 写响应接口 (B) -----------------------------------------------
input wire s_axi_bready, // 主机准备好接受写响应
output wire s_axi_bvalid, // 写响应有效
output wire [AXI_IDWIDTH-1:0] s_axi_bid, // 写响应ID
output wire [ 1:0] s_axi_bresp, // 写响应状态,两位用于指示成功或错误情况
// AXI-MM 读地址接口 (AR) -----------------------------------------------
output wire s_axi_arready, // 读地址通道准备好信号
input wire s_axi_arvalid, // 读地址通道有效信号
input wire [ 63:0] s_axi_araddr, // 读地址
input wire [ 7:0] s_axi_arlen, // 读突发长度
input wire [AXI_IDWIDTH-1:0] s_axi_arid, // 读事务ID
// AXI-MM 读数据接口 (R) -----------------------------------------------
input wire s_axi_rready, // 主机准备好接受读数据
output wire s_axi_rvalid, // 读数据有效信号
output wire s_axi_rlast, // 读数据突发的最后一个数据
output reg [ 63:0] s_axi_rdata, // 读数据
output wire [AXI_IDWIDTH-1:0] s_axi_rid, // 读响应ID
output wire [ 1:0] s_axi_rresp // 读响应状态
);
// ---------------------------------------------------------------------------------------
// AXI 读状态机
// ---------------------------------------------------------------------------------------
// 定义读状态机的状态
enum reg [0:0] {R_IDLE, R_BUSY} rstate = R_IDLE;
// 读状态相关的寄存器
reg [AXI_IDWIDTH-1:0] rid = '0; // 读响应ID寄存器
reg [ 7:0] rcount = '0; // 读计数器
reg [ 63-3:0] raddr_63_3, raddr_63_3_r; // 当前读地址的高60位及其节拍寄存器, 这是向字节对齐的
// 合成完整读地址
wire [ 63:0] raddr = {raddr_63_3 , 3'h0}; // 当前地址(这是完整的64位) 直接连线的 63:5 是作为out_buf的下标, 用来获取 out_buf_rdata
wire [ 63:0] raddr_r = {raddr_63_3_r, 3'h0}; // 上个时钟周期的地址, 直接连线的 如果大于h0100_0000, 则低三位是0, 4:3 是作为out_buf_rdata 256bit 控制位用来得到64bit
// 如果等于 0b0或者0b1000 或者0b10000, 则分别对应不同的处理
// 读接口信号分配
assign s_axi_arready = (rstate == R_IDLE); // 准备好接受新的读地址
assign s_axi_rvalid = (rstate == R_BUSY); // 读数据有效
assign s_axi_rlast = (rstate == R_BUSY) && (rcount == 8'd0); // 最后一个读数据
assign s_axi_rid = rid; // 返回读响应ID
assign s_axi_rresp = '0; // 默认响应成功
// 读状态机的时序逻辑
always @ (posedge clk or negedge rstn)
if (~rstn) begin // 复位逻辑
rstate <= R_IDLE;
rid <= '0;
rcount <= '0;
end else begin
case (rstate)
R_IDLE : // 空闲态
if (s_axi_arvalid) begin // 接收到新的读地址
rstate <= R_BUSY;
rid <= s_axi_arid;
rcount <= s_axi_arlen;
end
R_BUSY : // 忙碌态,处理读请求
if (s_axi_rready) begin // 主机准备好接收数据
if (rcount == 8'd0) // 数据传输完毕
rstate <= R_IDLE;
rcount <= rcount - 8'd1; // 减少剩余传输计数
end
endcase
end
// 组合逻辑处理读地址更新
always_comb
if (rstate == R_IDLE && s_axi_arvalid)
raddr_63_3 = s_axi_araddr[63:3]; // 新地址到来时更新
else if (rstate == R_BUSY && s_axi_rready)
raddr_63_3 = raddr_63_3_r + 61'h1; // 每次读取后地址递增
else
raddr_63_3 = raddr_63_3_r; // 保持不变
// 地址节拍寄存器更新
always @ (posedge clk)
raddr_63_3_r <= raddr_63_3;
// ---------------------------------------------------------------------------------------
// AXI WRITE state machine
// ---------------------------------------------------------------------------------------
// 定义一个枚举类型来表示写状态机的三种状态:空闲(W_IDLE)、忙碌(W_BUSY)、响应(W_RESP)当前状态初始化为W_IDLE
enum reg [1:0] {W_IDLE, W_BUSY, W_RESP} wstate = W_IDLE;
// 定义寄存器来存储写操作的ID、剩余突发传输次数、以及写地址的高位30位
reg [AXI_IDWIDTH-1:0] wid = '0; // 写响应ID
reg [ 7:0] wcount = '0; // 突发传输剩余次数
reg [ 63-3:0] waddr_63_3 = '0; // 写地址的高位60位
// 组合逻辑生成完整的写地址通过拼接高位和固定的低位3位'0'
wire [ 63:0] waddr = {waddr_63_3, 3'h0};
// AXI写接口信号的控制逻辑分配
assign s_axi_awready = (wstate == W_IDLE); // 写地址通道准备好仅当状态为W_IDLE时有效
assign s_axi_wready = (wstate == W_BUSY); // 写数据通道准备好仅当状态为W_BUSY时有效
assign s_axi_bvalid = (wstate == W_RESP); // 写响应有效仅当状态为W_RESP时有效
assign s_axi_bid = wid; // 写响应ID直接赋值
assign s_axi_bresp = '0; // 写响应状态默认为成功('00
// 写状态机的时序逻辑
always @ (posedge clk or negedge rstn) // 在时钟上升沿或复位下降沿触发
if (~rstn) begin // 复位条件
wstate <= W_IDLE; // 状态重置为W_IDLE
wid <= '0; // 写ID清零
wcount <= '0; // 突发计数清零
waddr_63_3 <= '0; // 写地址高位清零
end else begin
case (wstate) // 根据当前状态执行相应的操作
W_IDLE : // 空闲状态
if (s_axi_awvalid) begin // 如果收到有效的写地址
wstate <= W_BUSY; // 转换到忙碌状态
wid <= s_axi_awid; // 记录写ID
wcount <= s_axi_awlen; // 记录突发长度
waddr_63_3 <= s_axi_awaddr[63:3]; // 更新写地址的高位部分
end
W_BUSY : // 忙碌状态,处理写数据
if (s_axi_wvalid) begin // 收到有效的写数据
if (wcount == 8'd0 || s_axi_wlast) // 数据传输完成或当前是突发的最后一个数据
wstate <= W_RESP; // 转换到响应状态
wcount <= wcount - 8'd1; // 减少剩余突发次数
waddr_63_3 <= waddr_63_3 + 61'h1; // 地址自增,准备下一次传输
end
W_RESP : // 响应状态,等待主机确认
if (s_axi_bready) // 如果主机准备好接收响应
wstate <= W_IDLE; // 回到空闲状态,准备下一次写操作
default : // 防御性编程,不期望的状态
wstate <= W_IDLE; // 强制回到空闲状态
endcase
end
// 自定义函数用于将大端字节序的32字节数据转换为小端字节序
function automatic logic [255:0] big_endian_to_little_endian_32B (input logic [255:0] din);
logic [255:0] dout;
for (int i=0; i<32; i++) // 遍历每一个字节
dout[i*8 +: 8] = din[(31-i)*8 +: 8]; // 交换字节位置
return dout;
endfunction
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// signals of the MPEG2 encoder
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
reg mpeg2_rstn = '0; // MPEG2模块的复位信号初始为低电平复位状态
wire mpeg2_sequence_busy; // 表示MPEG2序列处理是否正在进行的标志
reg mpeg2_sequence_stop = '0; // 控制停止MPEG2序列处理的信号初始为不阻止
reg [ 6:0] mpeg2_xsize16 = '0; // 存储MPEG2视频帧宽度的寄存器以16像素为单位
reg [ 6:0] mpeg2_ysize16 = '0; // 存储MPEG2视频帧高度的寄存器以16像素为单位
reg mpeg2_i_en = '0; // 表示是否有YUV数据输入使能的标志
reg [ 7:0] mpeg2_i_Y0, mpeg2_i_Y1, mpeg2_i_Y2, mpeg2_i_Y3; // Y分量的输入数据寄存器
reg [ 7:0] mpeg2_i_U0, mpeg2_i_U2 ; // U分量的输入数据寄存器
reg [ 7:0] mpeg2_i_V0, mpeg2_i_V2 ; // V分量的输入数据寄存器
wire mpeg2_o_en; // 表示MPEG2输出数据有效性的标志
wire mpeg2_o_last; // 表示当前输出是MPEG2流中的最后一个数据包的标志
wire [255:0] mpeg2_o_data; // 输出的MPEG2编码数据
reg [ 15:0] mpeg2_o_addr = '0; // 这个是内部用来确定保存位置的, 每次保存都加1, 用来当做out_buf 的下标, 指向输出缓冲区的当前需要保存的下标
reg mpeg2_o_over = '0; // 1: 溢出指示,表示输出地址超出预期范围
reg [255:0] out_buf ['h10000]; // 定义一个 BRAM 大容量的输出缓冲区用于暂存MPEG2 IP产生的输出数据大小为 256bit * 0x10000(也就是2的16次方) = 16777216bit = 2 MB
reg [255:0] out_buf_rdata; // 用于读取BRAM中数据的临时256位寄存器, 受 M传进来的raddr 控制
always @ (posedge clk) // 在时钟上升沿更新
out_buf_rdata <= out_buf[ (16)'(raddr>>5) ]; // raddr低5位去掉, 用来当做out_buf下标, 从axi传来的指定地址读取out_buf BRAM中256位的数据到临时寄存器
always @ (posedge clk) // 在时钟上升沿更新
if ( mpeg2_o_en & ~mpeg2_o_over ) // 当有有效输出且未溢出时
out_buf[mpeg2_o_addr] <= big_endian_to_little_endian_32B(mpeg2_o_data); // 将输出数据写入缓冲区并转换字节序
always_comb // 组合逻辑,立即响应输入变化
if ( raddr_r == 64'h00000000 ) // 读取状态寄存器(复位和序列状态)
s_axi_rdata = {61'h0, mpeg2_sequence_busy, 1'b0, mpeg2_rstn};
else if ( raddr_r == 64'h00000008 ) // 读取视频帧尺寸 0b1000
s_axi_rdata = {25'h0, mpeg2_ysize16,
25'h0, mpeg2_xsize16 };
else if ( raddr_r == 64'h00000010 ) // 读取输出缓冲区状态 0b10000
s_axi_rdata = { 31'h0, mpeg2_o_over,
11'h0, mpeg2_o_addr, 5'h0};
else if ( raddr_r >= 64'h0100_0000 ) // 读取MPEG2输出流数据
case( raddr_r[4:3] ) // 这个64位地址, 低三位 是连线的0, 高位连接的raddr_63_3_r, 每个时钟周期会加1, 下面会在4个时钟周期之后, 进行依次运行了
// 通过 axi总线 把当前临时寄存器out_buf_rdata 中的值传到外侧
2'b00 : s_axi_rdata = out_buf_rdata[ 63: 0];
2'b01 : s_axi_rdata = out_buf_rdata[127: 64];
2'b10 : s_axi_rdata = out_buf_rdata[191:128];
2'b11 : s_axi_rdata = out_buf_rdata[255:192];
endcase
else
s_axi_rdata = '0; // 其他地址返回全零
always @ (posedge clk) begin
mpeg2_sequence_stop <= 1'b0; // 初始化停止信号为低
mpeg2_i_en <= 1'b0; // 初始化输入使能为低
if ( mpeg2_o_en ) begin
if ( mpeg2_o_addr == '1 ) // 检查地址是否达到最大值,表示溢出
mpeg2_o_over <= 1'b1; // 设置溢出标志
else
mpeg2_o_addr <= mpeg2_o_addr + 16'h1; // 否则递增地址
end
if ( s_axi_wvalid & s_axi_wready ) begin // 当写操作有效且就绪时
if ( waddr == 64'h00000000 ) begin // 写入控制寄存器(复位和序列控制)
mpeg2_rstn <= s_axi_wdata[0]; // 更新复位信号
mpeg2_sequence_stop <= s_axi_wdata[1]; // 更新序列停止控制
end else if ( waddr == 64'h00000008 ) begin // 写入视频帧尺寸 0b1000
mpeg2_xsize16 <= s_axi_wdata[ 6: 0]; // 更新宽度
mpeg2_ysize16 <= s_axi_wdata[38:32]; // 更新高度
end else if ( waddr == 64'h00000010 ) begin // 清除输出缓冲区控制 0b1_0000
mpeg2_o_addr <= '0; // 重置地址
mpeg2_o_over <= '0; // 重置溢出标志
end else if ( waddr >= 64'h0100_0000 ) begin // 写入原始YUV像素数据
mpeg2_i_en <= 1'b1; // 打开输入, 把下面的数据传进去, 后面会进行计算, 计算完之后会保存到 out_buf[mpeg2_o_addr] 中
{ mpeg2_i_V2, mpeg2_i_Y3, mpeg2_i_U2, mpeg2_i_Y2, // 分配YUV数据到相应寄存器, 这里每个寄存器是8bit, 一共64bit
mpeg2_i_V0, mpeg2_i_Y1, mpeg2_i_U0, mpeg2_i_Y0 } <= s_axi_wdata;
end
end
end
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MPEG2 encoder instance
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
mpeg2encoder #(
.XL ( 6 ), // determine the max horizontal pixel count. 4->256 pixels 5->512 pixels 6->1024 pixels 7->2048 pixels .
.YL ( 6 ), // determine the max vertical pixel count. 4->256 pixels 5->512 pixels 6->1024 pixels 7->2048 pixels .
.VECTOR_LEVEL ( 3 ),
.Q_LEVEL ( 2 )
) mpeg2encoder_i (
.rstn ( mpeg2_rstn ),
.clk ( clk ),
// Video sequence configuration interface.
.i_xsize16 ( mpeg2_xsize16 ),
.i_ysize16 ( mpeg2_ysize16 ),
.i_pframes_count ( 8'd47 ),
// Video sequence input pixel stream interface. In each clock cycle, this interface can input 4 adjacent pixels in a row. Pixel format is YUV 4:4:4, the module will convert it to YUV 4:2:0, then compress it to MPEG2 stream.
.i_en ( mpeg2_i_en ),
.i_Y0 ( mpeg2_i_Y0 ),
.i_Y1 ( mpeg2_i_Y1 ),
.i_Y2 ( mpeg2_i_Y2 ),
.i_Y3 ( mpeg2_i_Y3 ),
.i_U0 ( mpeg2_i_U0 ),
.i_U1 ( mpeg2_i_U0 ),
.i_U2 ( mpeg2_i_U2 ),
.i_U3 ( mpeg2_i_U2 ),
.i_V0 ( mpeg2_i_V0 ),
.i_V1 ( mpeg2_i_V0 ),
.i_V2 ( mpeg2_i_V2 ),
.i_V3 ( mpeg2_i_V2 ),
// Video sequence control interface.
.i_sequence_stop ( mpeg2_sequence_stop ),
.o_sequence_busy ( mpeg2_sequence_busy ),
// Video sequence output MPEG2 stream interface.
.o_en ( mpeg2_o_en ),
.o_last ( mpeg2_o_last ),
.o_data ( mpeg2_o_data )
);
endmodule