實現思路
在實現上,由于bmp除去文件頭后也只是把圖像流數據按順序放而已,所以這里
- 先用一個fifo緩存圖像數據
- 寫一個狀態機控制按順序輸出文件頭和數據。
- 注意fifo的讀寫和AXIS之間的握手和控制邏輯。因為看起來fifo是暫存數據的,但可預見fifo應該是有可能周期性空的,因為在每行的結束后tlast都是讓valid拉低一個周期,這一小個周期在行多了之后一點會抵消文件頭的大小。
生成緩存fifo
聲明:
- 命名并不規范
- 可以用原語(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