From deab553d218f386e87e5583f9e2f4230861040ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=B3=E5=85=89=E5=B0=91=E5=B9=B4?= <849317537@qq.com> Date: Wed, 29 May 2024 17:58:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=80=E4=BA=9B=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E7=94=A8=E4=BA=8E=E5=AD=A6=E4=B9=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xdma/ch_3/axi_mpeg2encoder_wrapper_cn.sv | 296 +++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 xdma/ch_3/axi_mpeg2encoder_wrapper_cn.sv diff --git a/xdma/ch_3/axi_mpeg2encoder_wrapper_cn.sv b/xdma/ch_3/axi_mpeg2encoder_wrapper_cn.sv new file mode 100644 index 0000000..f2d2465 --- /dev/null +++ b/xdma/ch_3/axi_mpeg2encoder_wrapper_cn.sv @@ -0,0 +1,296 @@ +// 定义一个名为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}; // 上个时钟周期的地址, 直接连线的 低三位是0, 4:3 是作为out_buf_rdata 256bit 控制位用来得到64bit + +// 读接口信号分配 +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; // 这个是内部用来确定保存位置的, 存储输出数据地址的寄存器,指向输出缓冲区的当前地址 +reg mpeg2_o_over = '0; // 1: 溢出指示,表示输出地址超出预期范围 + +reg [255:0] out_buf ['h10000]; // 定义一个大容量的输出缓冲区,用于暂存MPEG2 IP产生的输出数据,大小为 256bit * 0x10000 = 16777216bit = 2 MB + +reg [255:0] out_buf_rdata; // 用于读取BRAM中数据的临时寄存器 + +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 // 写入视频帧尺寸 + mpeg2_xsize16 <= s_axi_wdata[ 6: 0]; // 更新宽度 + mpeg2_ysize16 <= s_axi_wdata[38:32]; // 更新高度 + + end else if ( waddr == 64'h00000010 ) begin // 清除输出缓冲区控制 + mpeg2_o_addr <= '0; // 重置地址 + mpeg2_o_over <= '0; // 重置溢出标志 + + end else if ( waddr >= 64'h01000000 ) begin // 写入原始YUV像素数据 + mpeg2_i_en <= 1'b1; // 使能输入 + { mpeg2_i_V2, mpeg2_i_Y3, mpeg2_i_U2, mpeg2_i_Y2, // 分配YUV数据到相应寄存器 + 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 + +