system-verilogbmp

Reading bitmap using SystemVerilog


VHDL is not good with binary files. I am considering using SystemVerilog for this and thinking how it could read bitmap file (image format .bmp) and process it. This shall be used to create a model to compare the RTL code against.

Many years ago I did something like this in C++:

struct BMPFileHeader {
    uint16_t file_type{0x4D42};          // File type always BM which is 0x4D42
    uint32_t file_size{0};               // Size of the file (in bytes)
    uint16_t reserved1{0};               // Reserved, always 0
    uint16_t reserved2{0};               // Reserved, always 0
    uint32_t offset_data{0};             // Start position of pixel data (bytes from the beginning of the file)
};

Then I had to apply pragma to ensure that the struct was packed:

#pragma pack(push, 1)

// ... your packed structures

#pragma pack(pop)

I could then read the file headers into the packed struct using something like this:

 void read(const char *fname) {
     std::ifstream inp{ fname, std::ios_base::binary };
     if (inp) {
         inp.read((char*)&file_header, sizeof(file_header));
         if(file_header.file_type != 0x4D42) {
             throw std::runtime_error("Error! Unrecognized file format.");
         }
         inp.read((char*)&bmp_info_header, sizeof(bmp_info_header));
...

Is the same possible with SystemVerilog as well for reading and writing binary files?


Solution

  • I was able to write a basic program that opens bitmap and stores the data into a dynamic array. Here it is:

    module tb_bmp;
    
      // 14 bytes of file header
      typedef struct packed {
        shortint unsigned file_type;          // File type always BM (0x4D42)
        int      unsigned file_size;          // Size of the file in bytes
        shortint unsigned reserved1;          // Reserved, always 0
        shortint unsigned reserved2;          // Reserved, always 0
        int      unsigned offset_data;        // Offset where the pixel data starts
      } BMPFileHeader;
    
      // 40 bytes of info header
      typedef struct packed {
        int      unsigned header_size;        // Size of the header
        int      signed   width;              // Width of the image
        int      signed   height;             // Height of the image
        shortint unsigned color_planes;       // Number of color planes, must be 1
        shortint unsigned bits_per_pixel;     // Number of bits per pixel
        int      unsigned compression_method; // Compression type (0 = uncompressed)
        int      unsigned image_size;         // Size of the raw bitmap data
        int      signed   x_pixels_per_meter; // Horizontal resolution of the image. (pixel per metre, signed integer)
        int      signed   y_pixels_per_meter; // Vertical resolution of the image. (pixel per metre, signed integer)
        int      unsigned colors_used;        // Number of colors used
        int      unsigned important_colors;   // Number of important colors
      } BMPInfoHeader;
    
      typedef struct packed {
        byte b;   // Red component
        byte g;   // Green component
        byte r;   // Blue component
      } pixel_t;
    
      // File variables
      int file;
      int result;
      int img_h;
      int img_w;
      int img_h_8;
      int img_w_8;
      int offset;
      int pixel;
      int ar_index;
    
      BMPFileHeader file_header;
      BMPInfoHeader info_header;
      pixel_t pixel_data;
      pixel_t pixel_data_ar[];
    
      initial begin
    
          // Open the BMP file in binary read mode
          file = $fopen("image.bmp", "rb");
    
          if (file == 0) begin
              $display("Error: Cannot open file.");
              $finish;
          end
          else begin
            $display("BMP opened successfully");
          end
    
          // Read BMPFileHeader (14 bytes)
          $fread(file_header, file);
          $display("%x", file_header);
    
          if (file_header.file_type != 16'h424D) begin
              $display("Error: Not a BMP file.");
              $fclose(file);
              $stop;
          end
    
          // Read BMPInfoHeader (40 bytes)
          $fread(info_header, file);
          $display("%x", info_header);
    
          img_h  = {<<byte {info_header.width}};
          img_w  = {<<byte {info_header.height}};
          offset = {<<byte {file_header.offset_data}};
    
          img_h_8 = img_h + (img_h % 8);
          img_w_8 = img_w + (img_w % 8);
    
          // Print the bitmap file while converting the big endian data to little endian
          $display("   info header : %0d", {<<byte {info_header.header_size}});
          $display("     BMP Width : %0d", img_w);
          $display("    BMP Height : %0d", img_h);
          $display("Bits per pixel : %0d", {<<byte {info_header.bits_per_pixel}});
          $display("   Data offset : %0d", {<<byte {file_header.offset_data}});
    
          // Move file pointer to start of pixel data to ensure all headers are skipped
          result = $fseek(file, offset, 0);
          $display($ftell(file));
    
          if (result != 0) begin
            $display("Error: fseek failed.");
            $stop;
          end
    
          // Allocate the outer dynamic array for rows (height)
          pixel_data_ar = new[img_h_8*img_w_8];  // Allocate for the number of rows
    
          // read the image data and store it upside down in the pixel data array
          $display("\nread file pixel data \n");
          for (int h = 0; h < img_h; h++) begin
            for (int w = 0; w < img_w; w++) begin
            // for (int i = 0; i < 10; i++) begin
              $fread(pixel_data, file);
              ar_index = (img_h-1-h)*img_h + w;
              pixel_data_ar[ar_index].r = pixel_data.r;
              pixel_data_ar[ar_index].g = pixel_data.g;
              pixel_data_ar[ar_index].b = pixel_data.b;
    
              $display("RGB [%u,%u] %u, %u, %u", w, h, pixel_data.r, pixel_data.g, pixel_data.b);
    
            end
          end
    
          // print the accumulated pixel data into the terminal
          $display("\nread extracted pixel data \n");
          for (int h = 0; h < img_h; h++) begin
            for (int w = 0; w < img_w; w++) begin
              ar_index = h*img_h + w;
              pixel_data.r = pixel_data_ar[ar_index].r;
              pixel_data.g = pixel_data_ar[ar_index].g;
              pixel_data.b = pixel_data_ar[ar_index].b;
              $display("RGB [%u,%u] %u, %u, %u", w, h, pixel_data.r, pixel_data.g, pixel_data.b);
            end
          end
        
          // Close the file
          $fclose(file);
      end
    
    endmodule
    

    I will eventually store the pixel data into 8x8 blocks. This is why there are two variables that round the width and height to the nearest 8.