0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

图像流AXI-Stream生成BMP文件的实现思路

冬至子 来源:小何的芯像石头 作者:五线谱是偶然来的 2023-06-27 11:20 次阅读

实现思路

在实现上,由于bmp除去文件头后也只是把图像流数据按顺序放而已,所以这里

  1. 先用一个fifo缓存图像数据
  2. 写一个状态机控制按顺序输出文件头和数据。
  3. 注意fifo的读写和AXIS之间的握手和控制逻辑。因为看起来fifo是暂存数据的,但可预见fifo应该是有可能周期性空的,因为在每行的结束后tlast都是让valid拉低一个周期,这一小个周期在行多了之后一点会抵消文件头的大小。

生成缓存fifo

声明:

  1. 命名并不规范
  2. 可以用原语(xpm)或其他同类型IP生成,这里不多赘述。

在IP catalog的搜索框中写fifo,选FIFO Generator:

图片

具体按下面设置:

图片

这里注意必须选择First Word Fall Through

选25位是因为,在数据结构上是1tuser + 2*8 data,选择把帧开始标志也丢进fifo可以避免错帧。

总体端口

生成的BMP文件依然以AXIS格式输出,在tb中再以二进制格式写进文件:

module axis2bmp#(
    parameter PIC_HEIGHT = 1080,
    parameter PIC_WIDTH  = 1920
)(
    // global signal
    input clk_i,    // clock
    input rst_n_i,  // reset

    // axi stream (slave) interface signal - > pixel data
    input  [23:0] s_axis_video_tdata,   // DATA
    input  [0:0] s_axis_video_tvalid,  // VALID
    output [0:0] s_axis_video_tready,  // READY
    input  [0:0] s_axis_video_tuser,   // SOF
    input  [0:0] s_axis_video_tlast,    // EOL

    // axi stream (master) interface signal - > bmp
    output reg [23:0] m_axis_video_tdata,   // DATA
    output [ 0:0] m_axis_video_tvalid,  // VALID
    input  [ 0:0] m_axis_video_tready,  // (meaningless)
    output [ 0:0] m_axis_video_tlast    // end of file stream
);

slave端为图像数据,master端为输出BMP文件流,这里需要注意master流中并不处理反压问题(即没有ready信号,懒得加fifo)

fifo接口逻辑

// image pixel fifo dw=24, BRAM cap=512
wire [24:0] bmp_header_din;
wire [0:0] bmp_header_wr;
wire [0:0] bmp_header_full;

wire [0:0] bmp_header_empty;
wire [0:0] bmp_header_rd;
wire [24:0] bmp_header_dout;

bmp_header bmp_header_inst (
    .clk(clk_i),      // input wire clk
    .srst(~rst_n_i),    // input wire srst
    .din(bmp_header_din),      // input wire [23 : 0] din
    .wr_en(bmp_header_wr),  // input wire wr_en
    .rd_en(bmp_header_rd),  // input wire rd_en
    .dout(bmp_header_dout),    // output wire [23 : 0] dout
    .full(bmp_header_full),    // output wire full
    .empty(bmp_header_empty)  // output wire empty
);

// pixel fifo assignment
assign bmp_header_din = {s_axis_video_tuser,s_axis_video_tdata};
assign s_axis_video_tready = ~bmp_header_full;
assign bmp_header_wr = s_axis_video_tready && s_axis_video_tvalid;

fifo的读使能放到后面再讲,这里先处理好数据进来就可以了

文件流处理状态机

经典三板斧,不展开

包头数据准备

需要搬回第一篇中的BMP文件格式,由于是输出,所以我们就不考虑调色板了:

图片

这里先用一些localparam存起来,(这里考虑大小不变)

//--------------------------写BMP状态机------------------------

// local parameter
localparam [15:0] bfType = 16'h4d42;
localparam [31:0] bfReserved = 32'h0000_0000;
localparam [31:0] biSizeImage = PIC_HEIGHT * PIC_WIDTH * 3;
localparam [31:0] biSizeImage_cnt = PIC_HEIGHT * PIC_WIDTH;
localparam [31:0] bfOffset = 32'd54;
localparam [31:0] bfSize = biSizeImage + bfOffset;

localparam [31:0] biSize = 32'h28;
localparam [31:0] biWidth = PIC_WIDTH;
localparam [31:0] biHeight = PIC_HEIGHT;

localparam [15:0] biPlanes = 16'd1;
localparam [15:0] biBitCount = 16'd24;
localparam [31:0] biCompression = 32'd0;
localparam [127:0] biUseless = 128'd0;

localparam   CNT_PIXEL  = $clog2(PIC_HEIGHT*PIC_WIDTH);

转移状态

//转移状态
localparam S_WAIT               = 3'b001 ;  // 等待SOF标记
localparam S_WRITE_HEADER       = 3'b010 ;  // 写BMP包头
localparam S_WRITE_DATA         = 3'b100 ;  // 写BMP数据

状态转移变量

//状态转移变量
reg [2:0] state, n_state;       // 状态寄存器
reg [4:0] header_cnt;           // 包头计数器
reg [CNT_PIXEL-1:0] pixel_cnt;  // 像素计数器

wire frame_start = bmp_header_dout[24];    // SOF flag
wire write_header_done = (header_cnt == 5'd17);     // 18 -1 - > 18*3
wire write_pixel_done = (pixel_cnt == biSizeImage_cnt -1'b1);

这里需要注意 : 两个状态只由计数器指定跳转

状态转移

//状态机初始化
always @ (posedge clk_i) begin
    if(~rst_n_i)
        state <= S_WAIT;
    else
        state <= n_state;
end

状态机 状态转移

always @ (*) begin
    case(state)
        S_WAIT :
            if(frame_start)
                n_state = S_WRITE_HEADER;
        else
            n_state = S_WAIT;
        S_WRITE_HEADER:
            if(write_header_done)
                n_state = S_WRITE_DATA;
        else
            n_state = S_WRITE_HEADER;
        S_WRITE_DATA:
            if(write_pixel_done)
                n_state = S_WAIT;
        else
            n_state = S_WRITE_DATA;
        default:
            n_state = S_WAIT;
    endcase
end

写BMP包头 处理逻辑

这里直接按照文件格式,用计数器怼进去进行:

always @(posedge clk_i or negedge  rst_n_i) begin
    if (~rst_n_i)
        header_cnt <= 5'd0;
    else if(state == S_WRITE_HEADER && header_cnt < 5'd17)
        header_cnt <= header_cnt + 1'd1;
    else
        header_cnt <= 5'd0;
end

在数据上,可参考(注意数据以小端输出):

case (header_cnt)
    5'd0 :
        m_axis_video_tdata = {bfSize[0+:8], bfType};
    5'd1 :
        m_axis_video_tdata = bfSize[8+:24];
    5'd2 :
        m_axis_video_tdata = bfReserved[0 +:24];
    5'd3 :
        m_axis_video_tdata = {bfOffset[0+:16],bfReserved[24+:8]};
    5'd4 :
        m_axis_video_tdata = {biSize[0+:8], bfOffset[16+:16]};
    5'd5 :
        m_axis_video_tdata = biSize[8+:24];
    5'd6 :
        m_axis_video_tdata = biWidth[0+:24];
    5'd7 :
        m_axis_video_tdata = {biHeight[0+:16],biWidth[24+:8]};
    5'd8 :
        m_axis_video_tdata = {biPlanes[0+:8],biHeight[16+:16]};
    5'd9 :
        m_axis_video_tdata = {biBitCount[0+:16],biPlanes[8+:8]};
    5'd10 :
        m_axis_video_tdata = biCompression[0+:24];
    5'd11 :
        m_axis_video_tdata = {biSizeImage[0+:16],biCompression[24+:8]};
    5'd12 :
        m_axis_video_tdata = {biUseless[0+:8], biSizeImage[16+:16]};
    5'd13 :
        m_axis_video_tdata = biUseless[8+:24];
    5'd14 :
        m_axis_video_tdata = biUseless[32+:24];
    5'd15 :
        m_axis_video_tdata = biUseless[56+:24];
    5'd16 :
        m_axis_video_tdata = biUseless[80+:24];
    5'd17 :
        m_axis_video_tdata = biUseless[104+:24];
    default:
        m_axis_video_tdata = 24'heeeeee;
endcase

其中+:和-:语法简介可以翻看笔者之前的文章或自行百度。这里这样写是为了看位宽方便一点

写图像数据

这里直接放开fifo数据就可以了,注意握手逻辑:

计数器逻辑:

always @(posedge clk_i or negedge  rst_n_i) begin
    if (~rst_n_i)
        pixel_cnt <= 'd0;
    else if(state == S_WRITE_DATA && pixel_cnt < biSizeImage_cnt-1) begin
        if(bmp_header_empty)
            pixel_cnt <= pixel_cnt;
        else
            pixel_cnt <= pixel_cnt + 1'd1;
    end
    else
        pixel_cnt <= 'd0;
end

数据放行:

assign bmp_header_rd = ((state == S_WRITE_DATA) && ~bmp_header_empty) 
           || ((state == S_WAIT) && ~frame_start);

………………
else if(state == S_WRITE_DATA) begin
    m_axis_video_tdata = bmp_header_dout;
end
………………

这里上下两块合成一个组合逻辑就变成m_axis_video_tdata的完整控制逻辑了

仿真tb编写

这里由前面的积累就比较简单,直接将上一节生成的AXIS输入例化即可:

定义localparam

// Parameters
localparam  data_out = "./a_ch.txt";
localparam  bmp_path = "./test.bmp";
localparam  bmp_path_out = "./test_out.bmp";
localparam  height = 1080;
localparam  width = 1920;

这里如果输入输出不一样可以在tb中分开定义

简单引个线

// AXI-Stream Ports
wire [0:0] m_axis_tvalid;
wire [0:0] m_axis_tready;
wire [23:0] m_axis_tdata;
wire [0:0] m_axis_tlast;
wire [0:0] m_axis_tuser;

bmp_tb
#(
    .data_out(data_out ),
    .bmp_path(bmp_path ),
    .height(height ),
    .width (width )
)
bmp_tb_dut (
    .clk_i (clk_i ),
    .rst_n_i (rst_n_i ),
    .m_axis_tvalid (m_axis_tvalid ),
    .m_axis_tready (m_axis_tready ),
    .m_axis_tdata (m_axis_tdata ),
    .m_axis_tlast (m_axis_tlast ),
    .m_axis_tuser  ( m_axis_tuser)
);


wire [23:0] m_axis_video_tdata;  //这才是想要的信号
wire [ 0:0] m_axis_video_tvalid;
wire [ 0:0] m_axis_video_tlast;

axis2bmp
#(
    .PIC_HEIGHT(height ),
    .PIC_WIDTH (width )
)
axis2bmp_dut (
    .clk_i (clk_i ),
    .rst_n_i (rst_n_i ),
    .s_axis_video_tdata (m_axis_tdata ),
    .s_axis_video_tvalid (m_axis_tvalid ),
    .s_axis_video_tready (m_axis_tready ),
    .s_axis_video_tuser (m_axis_tuser ),
    .s_axis_video_tlast (m_axis_tlast ),
    .m_axis_video_tdata (m_axis_video_tdata ),
    .m_axis_video_tvalid (m_axis_video_tvalid ),
    .m_axis_video_tready (1'b1 ),
    .m_axis_video_tlast  ( m_axis_video_tlast)
);

文件流写入

这里直接用$fwrite一个一个字节写进去就可以了:

integer iBmpFileId;
initial begin
    begin
        iBmpFileId = $fopen(bmp_path_out,"wb");
        #10 rst_n_i = 1'b0;
        #200 rst_n_i = 1'b1;

        while (!m_axis_video_tlast) begin
            @(negedge clk_i) ;
            if(m_axis_video_tvalid==1'b1)begin
                $fwrite(iBmpFileId, "%c", m_axis_video_tdata[7-:8]) ;
                $fwrite(iBmpFileId, "%c", m_axis_video_tdata[15-:8]) ;
                $fwrite(iBmpFileId, "%c", m_axis_video_tdata[23-:8]);
            end
        end
        $fclose(iBmpFileId);
        #2000;
        $finish;
    end
end

然后在sim_x-> behav -> xsim下面放好图像就可以了。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 仿真器
    +关注

    关注

    14

    文章

    1014

    浏览量

    83595
  • 计数器
    +关注

    关注

    32

    文章

    2253

    浏览量

    94297
  • BMP
    BMP
    +关注

    关注

    0

    文章

    48

    浏览量

    17028
  • 状态机
    +关注

    关注

    2

    文章

    492

    浏览量

    27462
  • FIFO存储
    +关注

    关注

    0

    文章

    103

    浏览量

    5955
收藏 人收藏

    评论

    相关推荐

    Zynq中AXI4-Lite和AXI-Stream功能介绍

    等于b0000(非缓冲和非缓存)。 5) 不支持互斥性操作。 Zynq中AXI-Stream功能 AXI-Stream协议作为一个标准接口,用于连接数据交换元
    的头像 发表于 09-27 11:33 8784次阅读
    Zynq中<b class='flag-5'>AXI</b>4-Lite和<b class='flag-5'>AXI-Stream</b>功能介绍

    新手求助,HLS实现opencv算法加速的IP在vivado的使用

    是video in to AXI4-Stream,接到DMA,而HLS生成的算法IP是AXI4-Stream in and out。我想把AXI-Stream信号输出接到HLS输出的I
    发表于 01-16 09:22

    请问AXI4-Stream到Video核心的技巧有什么?

    ,这是一个由数据视频生成器卡携带的模块。在这种情况下,我只使用了四个内核来实现简单设计,即(1)。视频输入到Axi4-Stream核心,(2)视频缩放器核心,(3)。 AXI4-Stream
    发表于 11-08 09:53

    请问可以使用AXI-Stream Broadcaster作为AXI开关吗?

    我们可以使用AXI-Stream Broadcaster作为AXI开关吗?如果可能,我们需要控制切换哪个信号?我想开发小型应用程序,它涉及广播AXI数据并将
    发表于 05-07 09:42

    AXI-stream数据传输过程

    AXI4-Stream的核心思想在于流式处理数据。    图 4‑58 AXI-Stream Interface  全局信号  1.ACLK 全局时钟信号,在上升沿时对信号采样。所有的输入信号都通过上升沿采集,所有
    发表于 01-08 16:52

    AXI4Stream总线的FPGA视频系统的开发研究

    基于AXI4Stream总线协议,在Xilinx公司提供的FPGA上实现了一个具有缺陷像素校正、色彩滤波阵列插值、图像降噪实时图像采集与显示功能的视频系统。
    发表于 11-17 08:58 4585次阅读

    一文详解ZYNQ中的DMA与AXI4总线

    在ZYNQ中,支持AXI-Lite,AXI4和AXI-Stream三种总线,但PS与PL之间的接口却只支持前两种,AXI-Stream只能在PL中
    的头像 发表于 09-24 09:50 5181次阅读
    一文详解ZYNQ中的DMA与<b class='flag-5'>AXI</b>4总线

    ZYNQ中DMA与AXI4总线

    和接口的构架 在ZYNQ中,支持AXI-Lite,AXI4和AXI-Stream三种总线,但PS与PL之间的接口却只支持前两种,AXI-Stream只能在PL中
    的头像 发表于 11-02 11:27 4273次阅读
    ZYNQ中DMA与<b class='flag-5'>AXI</b>4总线

    AXI-Stream代码

    AXI-Stream代码详解 AXI4-StreamAXI4的区别在于AXI4-Stream没有ADDR接口,这样就不涉及读写数据的概念了,只有简单的发送与接收说法,减少了延时,允许
    的头像 发表于 11-05 17:40 3481次阅读
    <b class='flag-5'>AXI-Stream</b>代码

    SystemVerilog常用可综合IP模块库

    了解决这个复杂的问题,设计了这个库,它可以帮助您简化设计流程。使用一些简单的 API 可以轻松地在测试台中读取和写入标准位图文件 (.BMP)。并且,使用 AXI-Stream Video
    的头像 发表于 05-05 09:48 1680次阅读

    关于AXI4-Stream协议总结分享

    XI4-StreamAXI4的区别就是AXI4-Stream去除了地址线,这样就不涉及读写数据的概念了,只有简单的发送与接收说法,减少了延时。由于AXI4-Stream协议(amba
    的头像 发表于 06-23 10:08 2246次阅读

    AXI4-Stream Video 协议和AXI_VDMA的IP核介绍

    本文主要介绍关于AXI4-Stream Video 协议和AXI_VDMA的IP核相关内容。为后文完成使用带有HDMI接口的显示器构建图像视频显示的测试工程做准备。
    的头像 发表于 07-03 16:11 8151次阅读

    AXI4 、 AXI4-Lite 、AXI4-Stream接口

    AXI4 是一种高性能memory-mapped总线,AXI4-Lite是一只简单的、低通量的memory-mapped 总线,而 AXI4-Stream 可以传输高速数据。从字面意
    的头像 发表于 07-04 09:40 8348次阅读

    LogiCORE IP AXI4-Stream FIFO内核解决方案

    LogiCORE IP AXI4-Stream FIFO内核允许以内存映射方式访问一个AXI4-Stream接口。该内核可用于与AXI4-Stream IP接口,类似于LogiCORE IP
    的头像 发表于 09-25 10:55 1352次阅读
    LogiCORE IP <b class='flag-5'>AXI4-Stream</b> FIFO内核解决方案

    AXI传输数据的过程

    AXI-Stream,其中AXI-Lite和AXI-Full都是基于memory map的形式实现数据传输(即包括地址总线),而AXI-Stream
    的头像 发表于 10-31 15:37 1068次阅读
    <b class='flag-5'>AXI</b>传输数据的过程