diff options
Diffstat (limited to 'bba')
-rw-r--r-- | bba/README.md | 74 | ||||
-rw-r--r-- | bba/bba.cmake | 13 | ||||
-rw-r--r-- | bba/main.cc | 427 |
3 files changed, 514 insertions, 0 deletions
diff --git a/bba/README.md b/bba/README.md new file mode 100644 index 00000000..166ff7ff --- /dev/null +++ b/bba/README.md @@ -0,0 +1,74 @@ +Binary Blob Assembler (bba) +=========================== + +This tools read a text file describing binary data, and write that binary data +file. The usual flow is that the input to bbasm is generated by a python +script, and the output is linked or loaded into a C program, using (packed) +structs to interpret the binary data. + +All references (pointers) are encoded als 32 bit byte offset relative to the +location of the pointer. This way the resulting binary blob is position +independent. + +Valid commands for the input are as follows. + +pre \<string\> +-------------- + +When a C file is generated as output, all the "pre" strings will be included +before the binary blob. + +post \<string\> +--------------- + +When a C file is generated as output, all the "post" strings will be included +after the binary blob. + +push \<name\> +------------- + +All following commands up until the matching "pop" will be writen to stream +\<name\>. Everything written to the same stream will end up in a continous +region of the output. The statements `pop`, `label`, `ref`, `u8`, `u16`, +`u32`, and `str` are only valid within such a block. The name used in the +first push statement also determines the name of the variable in the generated +C output (when C is selected as output file format). + +pop +--- + +End of a push..pop block. + +label \<name\> \[\<comment\>\] +------------------------------ + +Add a label for the current position. + +ref \<name\> \[\<comment\>\] +---------------------------- + +Add a 32-bit reference to the specified label. The reference will be a byte +offset relative to the memory location of the reference itself. + +u8 \<value\> \[\<comment\>\] +---------------------------- + +Add a 8-bit value to the binary blob. + +u16 \<value\> \[\<comment\>\] +----------------------------- + +Add a 16-bit value to the binary blob. Note that the input must be structured +in a way that ensures that all u16 are aligned to 2-byte addresses. + +u32 \<value\> \[\<comment\>\] +----------------------------- + +Add a 32-bit value to the binary blob. Note that the input must be structured +in a way that ensures that all u32 are aligned to 4-byte addresses. + +str "\<string\>" \[\<comment\>\] +-------------------------------- + +Add a reference to a zero-terminated copy of that string. Any character may be +used to quote the string, but the most common choices are `"` and `|`. diff --git a/bba/bba.cmake b/bba/bba.cmake new file mode 100644 index 00000000..3e094277 --- /dev/null +++ b/bba/bba.cmake @@ -0,0 +1,13 @@ +IF(CMAKE_CROSSCOMPILING) + SET(IMPORT_EXECUTABLES "IMPORTFILE-NOTFOUND" CACHE FILEPATH "Point it to the export file from a native build") + INCLUDE(${IMPORT_EXECUTABLES}) +ENDIF(CMAKE_CROSSCOMPILING) + +IF(NOT CMAKE_CROSSCOMPILING) + ADD_EXECUTABLE(bbasm bba/main.cc) + target_link_libraries(bbasm LINK_PUBLIC ${Boost_PROGRAM_OPTIONS_LIBRARY}) +ENDIF(NOT CMAKE_CROSSCOMPILING) + +IF(NOT CMAKE_CROSSCOMPILING) + EXPORT(TARGETS bbasm FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake ) +ENDIF(NOT CMAKE_CROSSCOMPILING) diff --git a/bba/main.cc b/bba/main.cc new file mode 100644 index 00000000..263cf39e --- /dev/null +++ b/bba/main.cc @@ -0,0 +1,427 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com> + * Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <assert.h> +#include <boost/program_options.hpp> +#include <iostream> +#include <map> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <string> +#include <vector> + +enum TokenType : int8_t +{ + TOK_LABEL, + TOK_REF, + TOK_U8, + TOK_U16, + TOK_U32 +}; + +struct Stream +{ + std::string name; + std::vector<TokenType> tokenTypes; + std::vector<uint32_t> tokenValues; + std::vector<std::string> tokenComments; +}; + +Stream stringStream; +std::vector<Stream> streams; +std::map<std::string, int> streamIndex; +std::vector<int> streamStack; + +std::vector<int> labels; +std::vector<std::string> labelNames; +std::map<std::string, int> labelIndex; + +std::vector<std::string> preText, postText; + +const char *skipWhitespace(const char *p) +{ + if (p == nullptr) + return ""; + while (*p == ' ' || *p == '\t') + p++; + return p; +} + +int main(int argc, char **argv) +{ + bool debug = false; + bool verbose = false; + bool bigEndian = false; + bool writeC = false; + char buffer[512]; + + namespace po = boost::program_options; + po::positional_options_description pos; + po::options_description options("Allowed options"); + options.add_options()("v", "verbose output"); + options.add_options()("d", "debug output"); + options.add_options()("b", "big endian"); + options.add_options()("c", "write c strings"); + options.add_options()("files", po::value<std::vector<std::string>>(), "file parameters"); + pos.add("files", -1); + + po::variables_map vm; + try { + po::parsed_options parsed = po::command_line_parser(argc, argv).options(options).positional(pos).run(); + + po::store(parsed, vm); + + po::notify(vm); + } catch (std::exception &e) { + std::cout << e.what() << "\n"; + return 1; + } + if (vm.count("v")) + verbose = true; + if (vm.count("d")) + debug = true; + if (vm.count("b")) + bigEndian = true; + if (vm.count("c")) + writeC = true; + + if (vm.count("files") == 0) { + printf("File parameters are mandatory\n"); + exit(-1); + } + std::vector<std::string> files = vm["files"].as<std::vector<std::string>>(); + if (files.size() != 2) { + printf("Input and output parameters must be set\n"); + exit(-1); + } + + FILE *fileIn = fopen(files.at(0).c_str(), "rt"); + assert(fileIn != nullptr); + + FILE *fileOut = fopen(files.at(1).c_str(), writeC ? "wt" : "wb"); + assert(fileOut != nullptr); + + while (fgets(buffer, 512, fileIn) != nullptr) { + std::string cmd = strtok(buffer, " \t\r\n"); + + if (cmd == "pre") { + const char *p = skipWhitespace(strtok(nullptr, "\r\n")); + preText.push_back(p); + continue; + } + + if (cmd == "post") { + const char *p = skipWhitespace(strtok(nullptr, "\r\n")); + postText.push_back(p); + continue; + } + + if (cmd == "push") { + const char *p = strtok(nullptr, " \t\r\n"); + if (streamIndex.count(p) == 0) { + streamIndex[p] = streams.size(); + streams.resize(streams.size() + 1); + streams.back().name = p; + } + streamStack.push_back(streamIndex.at(p)); + continue; + } + + if (cmd == "pop") { + streamStack.pop_back(); + continue; + } + + if (cmd == "label" || cmd == "ref") { + const char *label = strtok(nullptr, " \t\r\n"); + const char *comment = skipWhitespace(strtok(nullptr, "\r\n")); + Stream &s = streams.at(streamStack.back()); + if (labelIndex.count(label) == 0) { + labelIndex[label] = labels.size(); + if (debug) + labelNames.push_back(label); + labels.push_back(-1); + } + s.tokenTypes.push_back(cmd == "label" ? TOK_LABEL : TOK_REF); + s.tokenValues.push_back(labelIndex.at(label)); + if (debug) + s.tokenComments.push_back(comment); + continue; + } + + if (cmd == "u8" || cmd == "u16" || cmd == "u32") { + const char *value = strtok(nullptr, " \t\r\n"); + const char *comment = skipWhitespace(strtok(nullptr, "\r\n")); + Stream &s = streams.at(streamStack.back()); + s.tokenTypes.push_back(cmd == "u8" ? TOK_U8 : cmd == "u16" ? TOK_U16 : TOK_U32); + s.tokenValues.push_back(atoll(value)); + if (debug) + s.tokenComments.push_back(comment); + continue; + } + + if (cmd == "str") { + const char *value = skipWhitespace(strtok(nullptr, "\r\n")); + char terminator[2] = {*value, 0}; + assert(terminator[0] != 0); + value = strtok((char *)value + 1, terminator); + const char *comment = skipWhitespace(strtok(nullptr, "\r\n")); + std::string label = std::string("str:") + value; + Stream &s = streams.at(streamStack.back()); + if (labelIndex.count(label) == 0) { + labelIndex[label] = labels.size(); + if (debug) + labelNames.push_back(label); + labels.push_back(-1); + } + s.tokenTypes.push_back(TOK_REF); + s.tokenValues.push_back(labelIndex.at(label)); + if (debug) + s.tokenComments.push_back(comment); + stringStream.tokenTypes.push_back(TOK_LABEL); + stringStream.tokenValues.push_back(labelIndex.at(label)); + stringStream.tokenComments.push_back(""); + while (1) { + stringStream.tokenTypes.push_back(TOK_U8); + stringStream.tokenValues.push_back(*value); + if (debug) { + char char_comment[4] = {'\'', *value, '\'', 0}; + if (*value < 32 || *value >= 127) + char_comment[0] = 0; + stringStream.tokenComments.push_back(char_comment); + } + if (*value == 0) + break; + value++; + } + continue; + } + + assert(0); + } + + if (verbose) { + printf("Constructed %d streams:\n", int(streams.size())); + for (auto &s : streams) + printf(" stream '%s' with %d tokens\n", s.name.c_str(), int(s.tokenTypes.size())); + } + + assert(!streams.empty()); + assert(streamStack.empty()); + streams.push_back(Stream()); + streams.back().name = "strings"; + streams.back().tokenTypes.swap(stringStream.tokenTypes); + streams.back().tokenValues.swap(stringStream.tokenValues); + streams.back().tokenComments.swap(stringStream.tokenComments); + + int cursor = 0; + for (auto &s : streams) { + for (int i = 0; i < int(s.tokenTypes.size()); i++) { + switch (s.tokenTypes[i]) { + case TOK_LABEL: + labels[s.tokenValues[i]] = cursor; + break; + case TOK_REF: + cursor += 4; + break; + case TOK_U8: + cursor += 1; + break; + case TOK_U16: + assert(cursor % 2 == 0); + cursor += 2; + break; + case TOK_U32: + assert(cursor % 4 == 0); + cursor += 4; + break; + default: + assert(0); + } + } + } + + if (verbose) { + printf("resolved positions for %d labels.\n", int(labels.size())); + printf("total data (including strings): %.2f MB\n", double(cursor) / (1024 * 1024)); + } + + std::vector<uint8_t> data(cursor); + + cursor = 0; + for (auto &s : streams) { + if (debug) + printf("-- %s --\n", s.name.c_str()); + + for (int i = 0; i < int(s.tokenTypes.size()); i++) { + uint32_t value = s.tokenValues[i]; + int numBytes = 0; + + switch (s.tokenTypes[i]) { + case TOK_LABEL: + break; + case TOK_REF: + value = labels[value] - cursor; + numBytes = 4; + break; + case TOK_U8: + numBytes = 1; + break; + case TOK_U16: + numBytes = 2; + break; + case TOK_U32: + numBytes = 4; + break; + default: + assert(0); + } + + if (bigEndian) { + switch (numBytes) { + case 4: + data[cursor++] = value >> 24; + data[cursor++] = value >> 16; + /* fall-through */ + case 2: + data[cursor++] = value >> 8; + /* fall-through */ + case 1: + data[cursor++] = value; + /* fall-through */ + case 0: + break; + default: + assert(0); + } + } else { + switch (numBytes) { + case 4: + data[cursor + 3] = value >> 24; + data[cursor + 2] = value >> 16; + /* fall-through */ + case 2: + data[cursor + 1] = value >> 8; + /* fall-through */ + case 1: + data[cursor] = value; + /* fall-through */ + case 0: + break; + default: + assert(0); + } + cursor += numBytes; + } + + if (debug) { + printf("%08x ", cursor - numBytes); + for (int k = cursor - numBytes; k < cursor; k++) + printf("%02x ", data[k]); + for (int k = numBytes; k < 4; k++) + printf(" "); + + unsigned long long v = s.tokenValues[i]; + + switch (s.tokenTypes[i]) { + case TOK_LABEL: + if (s.tokenComments[i].empty()) + printf("label %s\n", labelNames[v].c_str()); + else + printf("label %-24s %s\n", labelNames[v].c_str(), s.tokenComments[i].c_str()); + break; + case TOK_REF: + if (s.tokenComments[i].empty()) + printf("ref %s\n", labelNames[v].c_str()); + else + printf("ref %-26s %s\n", labelNames[v].c_str(), s.tokenComments[i].c_str()); + break; + case TOK_U8: + if (s.tokenComments[i].empty()) + printf("u8 %llu\n", v); + else + printf("u8 %-27llu %s\n", v, s.tokenComments[i].c_str()); + break; + case TOK_U16: + if (s.tokenComments[i].empty()) + printf("u16 %-26llu\n", v); + else + printf("u16 %-26llu %s\n", v, s.tokenComments[i].c_str()); + break; + case TOK_U32: + if (s.tokenComments[i].empty()) + printf("u32 %-26llu\n", v); + else + printf("u32 %-26llu %s\n", v, s.tokenComments[i].c_str()); + break; + default: + assert(0); + } + } + } + } + + assert(cursor == int(data.size())); + + if (writeC) { + for (auto &s : preText) + fprintf(fileOut, "%s\n", s.c_str()); + + fprintf(fileOut, "const char %s[%d] =\n\"", streams[0].name.c_str(), int(data.size()) + 1); + + cursor = 1; + for (int i = 0; i < int(data.size()); i++) { + auto d = data[i]; + if (cursor > 70) { + fputc('\"', fileOut); + fputc('\n', fileOut); + cursor = 0; + } + if (cursor == 0) { + fputc('\"', fileOut); + cursor = 1; + } + if (d < 32 || d >= 127) { + if (i + 1 < int(data.size()) && (data[i + 1] < '0' || '9' < data[i + 1])) + cursor += fprintf(fileOut, "\\%o", int(d)); + else + cursor += fprintf(fileOut, "\\%03o", int(d)); + } else if (d == '\"' || d == '\'' || d == '\\') { + fputc('\\', fileOut); + fputc(d, fileOut); + cursor += 2; + } else { + fputc(d, fileOut); + cursor++; + } + } + + fprintf(fileOut, "\";\n"); + + for (auto &s : postText) + fprintf(fileOut, "%s\n", s.c_str()); + } else { + fwrite(data.data(), int(data.size()), 1, fileOut); + } + + return 0; +} |