Июн 21 2015

Генерация файлов .mif и case-конструкций

Пусть в проекте FPGA необходимо ПЗУ, в которое записана определённая информация, имеющаяся у нас в виде бинарного файла. ПЗУ, как правило, реализуются на блоках BRAM, в которые при инициализации FPGA записывается требуемое содержимое.
Файлы инициализации памяти BRAM для FPGA, могут иметь формат MIF (Memory Initialization File) или формат HEX.

DSC_0522_EDIT

Нужный модуль памяти можно сгенерировать в Quartus II с помощью MegaWizard, в ISE — с помощью Xilinx Core Generator. Кроме этого, в QuartusII возможно использование специальной директивы:

reg [7:0] mem[0:2047] /* synthesis ram_init_file = "test.mif" */;

Эта директива позволяет создать RAM, инициированную значениями из файла .MIF. Использование RAM с предустановленными значениями, это очень плохая идея, но в качестве ROM такую память использовать вполне можно. Проблема в том, что эту директиву понимает только Quartus II, никакой кроссплатформенности.

Quartus II предоставляет редактор файлов памяти, и возможность загрузки файлов .mif и .hex, Xilinx Core Generator требует загрузки файла .coe, имеющего другой формат, нежели MIF. Однако хочется иметь кроссплатформенное решение, которое не зависело бы от платформы. Таким вариантом является генерация ROM в виде case-конструкции:

always@(posedge clk)
begin
  case(in)
  ...
  11'h00d: out = 8'h99;
  11'h00e: out = 8'h81;
  11'h00f: out = 8'h7e;
  11'h010: out = 8'h7e;
  11'h011: out = 8'hff;
  11'h012: out = 8'hdb;
  11'h013: out = 8'hff;
  11'h014: out = 8'hc3;
  11'h015: out = 8'he7;
  11'h016: out = 8'hff;
  ...
  endcase
end

Полезная ссылка: различные конструкции для генерации разных типов RAM, ROM и других модулей можно найти в руководстве Altera «Recommended HDL Coding Styles»: https://www.altera.com/content/dam/altera-www/global/en_US/pdfs/literature/hb/qts/qts_qii51007.pdf
Я написал две простые утилиты, которая преобразует бинарный файл в MIF и в тело конструкции case.

Вот её полный исходник:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

std::vector readFile(const char* filename)
{
    // open the file:
    std::streampos fileSize;
    std::ifstream file(filename, std::ios::binary);
    // get its size:
    file.seekg(0, std::ios::end);
    fileSize = file.tellg();
    file.seekg(0, std::ios::beg);
    // read the data:
	std::vector fileData(fileSize);
    file.read((char*) &fileData[0], fileSize);
    return fileData;
}

unsigned getWidth(unsigned size)
{
	return size / 4 + ((size % 4)? 1: 0);
}

int main(int argc, char *argv[])
{
	if(argc < 5)
	{
		std::cout << "äîëæíû áûòü àðãóìåíòû: èìÿ_âõîäíîãî_ôàéëà èìÿ_âûõîäíîãî_ôàéëà ðàçìåð_âõîäíîé_ïåðåìåííîé ðàçìåð_âûõîäíîé_ïåðåìåííîé" << std::endl;
		std::cout << "íàïðèìåð: out_file.v 11 8" << std::endl;
		return 0;
	}
	std::vector inputFile = readFile(argv[1]);
	std::ofstream out(argv[2], std::ofstream::out);
	unsigned num = 0;
	unsigned inSize = atoi(argv[3]), outSize = atoi(argv[4]);
	unsigned inWidth = getWidth(inSize),
		depth = pow(2, inSize),
		outWidth = getWidth(outSize);
	out << "width=" << outSize << ";" << std::endl;
	out << "depth=" << depth << ";" << std::endl;
	out << "address_radix=hex;" << std::endl;
	out << "data_radix=hex;" << std::endl;
	out << "content begin" << std::endl;
	out.fill('0');
	for(auto ch:inputFile)
	{
		out << std::setw(inWidth) << std::hex << num++ << ": " << std::setw(outWidth) << std::right << std::hex << (int)ch << ";" << std::endl;
	}
	out << "end" << std::endl;
	out.close();
    return 0;
}

С её помощью можно преобразовать бинарный файл в MIF-формат, указав имя входного файла, имя выходного файла, размер переменной адреса в битах и размер данных в битах, например:

hex2mif "866.008" "008.mif" 11 8

Вторая утилита отличается незначительно:

  //основное отличие в этой строке:
  out << std::dec << std::setw(0) << inSize << "'h" << std::setw(inWidth) << std::hex << num++ << ": " << std::dec << std::setw(0) << outName << " = " << outSize << "'h" << std::setw(outWidth) << std::hex << (int)ch << ";" << std::endl;

В качестве пятого параметра ей нужно указывать имя выходного регистра:

hex2case "866.008" "008.mif" 11 8 out

Обе утилиты кроссплатформенные и могут быть скомпилированы как в Windows, так и в Linux.

Теперь можно использовать этот файл для инициализации памяти в verilog:

module rom_test(
  input wire clk, rst_n,
  input wire [10:0] in,
  output reg [7:0] out
);

always@(posedge clk)
begin
  case(in)
  `include "008.inc"
  endcase
end

endmodule

Ссылка на Github: https://github.com/arktur04/bin2mif