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