// 定义一个名为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


