Yes, VHDL can read and write binary files. However, there are no guarantees as to the exact file format, other than that you can read in a file you wrote out earlier, with the same version of the same simulator. (this was about ten years ago so forgive any haziness in the details)
By experiment, I found that in Modelsim. binary files are pretty much as expected (though I can't remember if it used a big-endian or little-endian byte order in the file).
Xilinx ISIM used the opposite endian-ness, and added/expected a 9 byte header before the data, rejecting files without that header. Xilinx also explicitly refused my request for documentation on the header format, so I resorted to extracting it from one file and prepending it to others, via head, tail and cat (on Linux, obviously). I used a boolean generic, "is_isim" to control endian-swapping according to the simulator.
I haven't tried porting these old testbenches to ghdl, but it is likely to be similar to Modelsim in its ease of use here.
In either case, you read binary data into an Tnteger, 32-bit words at a time, and translated from there into std_logic_vector or records or other types as appropriate (in my case, the files were SEG-Y format, a binary file used in geophysics)
If you're intimately familiar with Python, a script will be easier, but VHDL's strictness and emphasis on compile time checking makes getting the expected results via the VHDL route pretty easy once you get over the initial hump.
Binary file access...
From the LRM (VHDL-2008) (edited for brevity):
5.5 File types
5.5.1 General
file_type_definition ::= file of type_mark
Example :
type IntFile is file of integer;
5.5.2 File operations
Given the following file type declaration:
type FT is file of TM;
the following operations are implicitly declared immediately following the file type declaration:
procedure FILE_OPEN (file F: FT; External_Name: in STRING;
Open_Kind: in FILE_OPEN_KIND := READ_MODE);
procedure FILE_CLOSE (file F: FT);
procedure READ (file F: FT; VALUE: out TM);
procedure WRITE (file F: FT; VALUE: in TM);
procedure FLUSH (file F: FT);
function ENDFILE (file F: FT) return BOOLEAN;
Example:
procedure ReadFile is file MyFile : IntFile; variable i : integer; begin File_Open(MyFile,"beethoven_5.wav"); while not EndFile(MyFile) loop Read(MyFile,I); Audio_Dac <= I; end loop; File_Close(My_File); end ReadFile;
Note that this doesn't parse WAV files properly so will probably not play Beethoven's Fifth Symphony in your simulator. And if your file is a whole number of bytes, but not of 32-bit integers, you can treat it as a file of character and use character'pos and 'val attributes to translate to integers.
You may be able to treat it as a file of bit_vector(7 downto 0) but I haven't tried recently.