Merge pull request #74 from AndresNavarro82/machxo2-pullreq0
Bitstream decompression and better handling of crc & dummies (so they also work with MachXO2)
diff --git a/examples/ecp5_evn_multiboot/Makefile b/examples/ecp5_evn_multiboot/Makefile
new file mode 100644
index 0000000..948aad5
--- /dev/null
+++ b/examples/ecp5_evn_multiboot/Makefile
@@ -0,0 +1,23 @@
+all: multiboot.mcs
+
+%.json: %.v
+ yosys -p "synth_ecp5 -json $@" $<
+
+%_out.config: %.json
+ nextpnr-ecp5 --json $< --textcfg $@ --um5g-85k --package CABGA381 --lpf ecp5evn.lpf
+
+%.bit: %_out.config
+ ecppack $< $@
+
+
+multiboot.bin: blinky1.bit blinky2.bit
+ ecpmulti --flashsize 128 --input blinky1.bit --input blinky2.bit --address 0x00250000 $@
+
+multiboot.mcs: multiboot.bin
+ srec_cat $< -Binary -unfill 0xFF -Bit_Reverse=2 -Byte-Swap -o $@ -Intel
+
+clean:
+ rm -f *.svf *.bit *.config *.json multiboot.bin multiboot.mcs
+
+.PHONY: prog clean
+.PRECIOUS: blinky1.json blinky1_out.config blinky1.bit blinky2.json blinky2_out.config blinky2.bit multiboot.bin multiboot.mcs
diff --git a/examples/ecp5_evn_multiboot/README.md b/examples/ecp5_evn_multiboot/README.md
new file mode 100644
index 0000000..2f78a3a
--- /dev/null
+++ b/examples/ecp5_evn_multiboot/README.md
@@ -0,0 +1,18 @@
+# Purpose
+This example contains two separate verilog implementations
+1) Blinks LED 0 on EVN board
+2) Blinks LED 7 on EVN board
+
+It generates a multiboot.bin file as well as a suitable multiboot.mcs file.
+Once flashed, pressing PROGRAMN button will switch between the available
+bitstreams.
+
+# Usage
+In order to use, it must be flashed to the ECP5-EVN board.
+This can be done with the official programmer from lattice.
+The generated MCS (Intel Hex) file is sufficiently compatible with Lattice to
+allow flashing.
+However, flashing the bin/hex any other way should also work.
+Due to padding with SPI Flash erase byte, it will be significantly faster to
+flash the hex file.
+
diff --git a/examples/ecp5_evn_multiboot/blinky1.v b/examples/ecp5_evn_multiboot/blinky1.v
new file mode 100644
index 0000000..f4fac91
--- /dev/null
+++ b/examples/ecp5_evn_multiboot/blinky1.v
@@ -0,0 +1,9 @@
+module top(input clk, input btn, output [7:0] led);
+ reg [23:0] cnt = 0;
+
+ always@(posedge clk) begin
+ cnt <= cnt + 1;
+ end
+ assign led[0] = cnt[22];
+ assign led[7:1] = 7'b1111111;
+endmodule
diff --git a/examples/ecp5_evn_multiboot/blinky2.v b/examples/ecp5_evn_multiboot/blinky2.v
new file mode 100644
index 0000000..d2fc694
--- /dev/null
+++ b/examples/ecp5_evn_multiboot/blinky2.v
@@ -0,0 +1,9 @@
+module top(input clk, input btn, output [7:0] led);
+ reg [23:0] cnt = 0;
+
+ always@(posedge clk) begin
+ cnt <= cnt + 1;
+ end
+ assign led[7] = cnt[22];
+ assign led[6:0] = 7'b1111111;
+endmodule
diff --git a/examples/ecp5_evn_multiboot/ecp5evn.lpf b/examples/ecp5_evn_multiboot/ecp5evn.lpf
new file mode 100644
index 0000000..bd32c6d
--- /dev/null
+++ b/examples/ecp5_evn_multiboot/ecp5evn.lpf
@@ -0,0 +1,23 @@
+LOCATE COMP "clk" SITE "A10";
+IOBUF PORT "clk" IO_TYPE=LVCMOS33;
+
+LOCATE COMP "btn" SITE "P4";
+IOBUF PORT "btn" IO_TYPE=LVCMOS33;
+
+LOCATE COMP "led[0]" SITE "A13";
+LOCATE COMP "led[1]" SITE "A12";
+LOCATE COMP "led[2]" SITE "B19";
+LOCATE COMP "led[3]" SITE "A18";
+LOCATE COMP "led[4]" SITE "B18";
+LOCATE COMP "led[5]" SITE "C17";
+LOCATE COMP "led[6]" SITE "A17";
+LOCATE COMP "led[7]" SITE "B17";
+
+IOBUF PORT "led[0]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[1]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[2]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[3]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[4]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[5]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[6]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[7]" IO_TYPE=LVCMOS33;
diff --git a/examples/ulx3s_12k_multiboot/Makefile b/examples/ulx3s_12k_multiboot/Makefile
new file mode 100644
index 0000000..5d3a86b
--- /dev/null
+++ b/examples/ulx3s_12k_multiboot/Makefile
@@ -0,0 +1,23 @@
+all: multiboot.mcs
+
+%.json: %.v
+ yosys -p "synth_ecp5 -json $@" $<
+
+%_out.config: %.json
+ nextpnr-ecp5 --json $< --textcfg $@ --25k --package CABGA381 --lpf ulx3s.lpf
+
+%.bit: %_out.config
+ ecppack --idcode 0x21111043 $< $@
+
+
+multiboot.bin: blinky1.bit blinky2.bit
+ ecpmulti --input-idcode 0x41111043 --output-idcode 0x21111043 --flashsize 128 --input blinky1.bit --input blinky2.bit --address 0x00250000 $@
+
+multiboot.mcs: multiboot.bin
+ srec_cat $< -Binary -unfill 0xFF -Bit_Reverse=2 -Byte-Swap -o $@ -Intel
+
+clean:
+ rm -f *.svf *.bit *.config *.json multiboot.bin multiboot.mcs
+
+.PHONY: prog clean
+.PRECIOUS: blinky1.json blinky1_out.config blinky1.bit blinky2.json blinky2_out.config blinky2.bit multiboot.bin multiboot.mcs
diff --git a/examples/ulx3s_12k_multiboot/README.md b/examples/ulx3s_12k_multiboot/README.md
new file mode 100644
index 0000000..2f78a3a
--- /dev/null
+++ b/examples/ulx3s_12k_multiboot/README.md
@@ -0,0 +1,18 @@
+# Purpose
+This example contains two separate verilog implementations
+1) Blinks LED 0 on EVN board
+2) Blinks LED 7 on EVN board
+
+It generates a multiboot.bin file as well as a suitable multiboot.mcs file.
+Once flashed, pressing PROGRAMN button will switch between the available
+bitstreams.
+
+# Usage
+In order to use, it must be flashed to the ECP5-EVN board.
+This can be done with the official programmer from lattice.
+The generated MCS (Intel Hex) file is sufficiently compatible with Lattice to
+allow flashing.
+However, flashing the bin/hex any other way should also work.
+Due to padding with SPI Flash erase byte, it will be significantly faster to
+flash the hex file.
+
diff --git a/examples/ulx3s_12k_multiboot/blinky1.v b/examples/ulx3s_12k_multiboot/blinky1.v
new file mode 100644
index 0000000..70bf4f4
--- /dev/null
+++ b/examples/ulx3s_12k_multiboot/blinky1.v
@@ -0,0 +1,9 @@
+module top(input clk, output [7:0] led);
+ reg [23:0] cnt = 0;
+
+ always@(posedge clk) begin
+ cnt <= cnt + 1;
+ end
+ assign led[0] = cnt[22];
+ assign led[7:1] = 7'b1111111;
+endmodule
diff --git a/examples/ulx3s_12k_multiboot/blinky2.v b/examples/ulx3s_12k_multiboot/blinky2.v
new file mode 100644
index 0000000..9a7195f
--- /dev/null
+++ b/examples/ulx3s_12k_multiboot/blinky2.v
@@ -0,0 +1,9 @@
+module top(input clk, output [7:0] led);
+ reg [23:0] cnt = 0;
+
+ always@(posedge clk) begin
+ cnt <= cnt + 1;
+ end
+ assign led[7] = cnt[22];
+ assign led[6:0] = 7'b1111111;
+endmodule
diff --git a/examples/ulx3s_12k_multiboot/ulx3s.lpf b/examples/ulx3s_12k_multiboot/ulx3s.lpf
new file mode 100644
index 0000000..249f6e5
--- /dev/null
+++ b/examples/ulx3s_12k_multiboot/ulx3s.lpf
@@ -0,0 +1,20 @@
+LOCATE COMP "clk" SITE "G2";
+IOBUF PORT "clk" IO_TYPE=LVCMOS33;
+
+LOCATE COMP "led[7]" SITE "H3";
+LOCATE COMP "led[6]" SITE "E1";
+LOCATE COMP "led[5]" SITE "E2";
+LOCATE COMP "led[4]" SITE "D1";
+LOCATE COMP "led[3]" SITE "D2";
+LOCATE COMP "led[2]" SITE "C1";
+LOCATE COMP "led[1]" SITE "C2";
+LOCATE COMP "led[0]" SITE "B2";
+
+IOBUF PORT "led[0]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[1]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[2]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[3]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[4]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[5]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[6]" IO_TYPE=LVCMOS33;
+IOBUF PORT "led[7]" IO_TYPE=LVCMOS33;
diff --git a/libtrellis/CMakeLists.txt b/libtrellis/CMakeLists.txt
index 4a3b9c0..f780213 100644
--- a/libtrellis/CMakeLists.txt
+++ b/libtrellis/CMakeLists.txt
@@ -143,10 +143,15 @@
target_link_libraries(ecppll trellis ${Boost_LIBRARIES} ${link_param})
setup_rpath(ecppll)
+add_executable(ecpmulti ${INCLUDE_FILES} tools/ecpmulti.cpp)
+target_compile_definitions(ecpmulti PRIVATE TRELLIS_PREFIX="${CMAKE_INSTALL_PREFIX}")
+target_link_libraries(ecpmulti trellis ${Boost_LIBRARIES} ${link_param})
+setup_rpath(ecpmulti)
+
if (BUILD_SHARED)
- install(TARGETS trellis ecppack ecppll ecpunpack LIBRARY DESTINATION ${LIBDIR}/trellis RUNTIME DESTINATION bin)
+ install(TARGETS trellis ecppack ecppll ecpunpack ecpmulti LIBRARY DESTINATION ${LIBDIR}/trellis RUNTIME DESTINATION bin)
else()
- install(TARGETS ecppack ecpunpack ecppll RUNTIME DESTINATION bin)
+ install(TARGETS ecppack ecpunpack ecppll ecpmulti RUNTIME DESTINATION bin)
endif()
install(DIRECTORY ../database DESTINATION share/trellis PATTERN ".git" EXCLUDE)
install(DIRECTORY ../misc DESTINATION share/trellis)
diff --git a/libtrellis/include/Bitstream.hpp b/libtrellis/include/Bitstream.hpp
index e644f80..3d04cb6 100644
--- a/libtrellis/include/Bitstream.hpp
+++ b/libtrellis/include/Bitstream.hpp
@@ -15,6 +15,7 @@
namespace Trellis {
enum class BitstreamCommand : uint8_t {
SPI_MODE = 0b01111001,
+ JUMP = 0b01111110,
LSC_RESET_CRC = 0b00111011,
VERIFY_ID = 0b11100010,
LSC_WRITE_COMP_DIC = 0b00000010,
@@ -47,6 +48,7 @@
// Serialise a Chip back to a bitstream
static Bitstream serialise_chip(const Chip &chip, const map<string, string> options);
+ static Bitstream generate_jump(uint32_t address);
// Deserialise a bitstream to a Chip
Chip deserialise_chip();
diff --git a/libtrellis/include/Chip.hpp b/libtrellis/include/Chip.hpp
index 6d2e800..1c12c3b 100644
--- a/libtrellis/include/Chip.hpp
+++ b/libtrellis/include/Chip.hpp
@@ -132,6 +132,7 @@
// Miscellaneous information
uint32_t usercode = 0x0;
+ uint32_t ctrl0 = 0x40000000;
vector<string> metadata;
// Get max row and column
diff --git a/libtrellis/src/Bitstream.cpp b/libtrellis/src/Bitstream.cpp
index 8e391c9..1ea0bdb 100644
--- a/libtrellis/src/Bitstream.cpp
+++ b/libtrellis/src/Bitstream.cpp
@@ -29,6 +29,8 @@
{"dual-spi", 0x51},
{"qspi", 0x59}};
+static const uint32_t multiboot_flag = 1 << 20;
+
// The BitstreamReadWriter class stores state (including CRC16) whilst reading
// the bitstream
class BitstreamReadWriter {
@@ -353,6 +355,7 @@
case BitstreamCommand::LSC_PROG_CNTRL0: {
rd.skip_bytes(3);
uint32_t cfg = rd.get_uint32();
+ chip->ctrl0 = cfg;
BITSTREAM_DEBUG("set control reg 0 to 0x" << hex << setw(8) << setfill('0') << cfg);
}
break;
@@ -512,6 +515,12 @@
BITSTREAM_NOTE("SPI Mode " << spimode->first);
}
break;
+ case BitstreamCommand::JUMP:
+ rd.skip_bytes(3);
+ BITSTREAM_DEBUG("Jump command");
+ /* TODO: Parse address and SPI Flash read speed */
+ rd.skip_bytes(4);
+ break;
case BitstreamCommand::DUMMY:
break;
default: BITSTREAM_FATAL("unsupported command 0x" << hex << setw(2) << setfill('0') << int(cmd),
@@ -525,6 +534,36 @@
}
}
+Bitstream Bitstream::generate_jump(uint32_t address) {
+ BitstreamReadWriter wr;
+
+ // Dummy bytes
+ wr.insert_dummy(16);
+ // Preamble
+ wr.write_bytes(preamble.begin(), preamble.size());
+
+ // Padding
+ wr.insert_dummy(4);
+
+ // Dummy control register
+ wr.write_byte(uint8_t(BitstreamCommand::LSC_PROG_CNTRL0));
+ wr.insert_zeros(3);
+ wr.insert_zeros(4);
+
+ // Jump command
+ wr.write_byte(uint8_t(BitstreamCommand::JUMP));
+ wr.insert_zeros(3);
+ wr.write_byte(0x03); // TODO: Allow specifying SPI Flash read speed
+
+ wr.write_byte(uint8_t((address >> 16UL) & 0xFF));
+ wr.write_byte(uint8_t((address >> 8UL) & 0xFF));
+ wr.write_byte(uint8_t(address & 0xFF));
+
+ wr.insert_dummy(18);
+
+ return Bitstream(wr.get(), std::vector<string>());
+}
+
Bitstream Bitstream::serialise_chip(const Chip &chip, const map<string, string> options) {
BitstreamReadWriter wr;
// Preamble
@@ -555,7 +594,7 @@
// Set control reg 0 to 0x40000000
wr.write_byte(uint8_t(BitstreamCommand::LSC_PROG_CNTRL0));
wr.insert_zeros(3);
- uint32_t ctrl0 = 0x40000000;
+ uint32_t ctrl0 = chip.ctrl0;
if (options.count("freq")) {
auto freq = find_if(frequencies.begin(), frequencies.end(), [&](const pair<string, uint8_t> &fp){
return fp.first == options.at("freq");
@@ -564,6 +603,12 @@
throw runtime_error("bad frequency option " + options.at("freq"));
ctrl0 |= freq->second;
}
+ if (options.count("multiboot")) {
+ if (options.at("multiboot") == "yes")
+ ctrl0 |= multiboot_flag;
+ else
+ ctrl0 &= ~multiboot_flag;
+ }
wr.write_uint32(ctrl0);
// Init address
wr.write_byte(uint8_t(BitstreamCommand::LSC_INIT_ADDRESS));
diff --git a/libtrellis/tools/ecpmulti.cpp b/libtrellis/tools/ecpmulti.cpp
new file mode 100644
index 0000000..67d7965
--- /dev/null
+++ b/libtrellis/tools/ecpmulti.cpp
@@ -0,0 +1,212 @@
+#include "ChipConfig.hpp"
+#include "BitDatabase.hpp"
+#include "Bitstream.hpp"
+#include "Chip.hpp"
+#include "Database.hpp"
+#include "Tile.hpp"
+#include <iostream>
+#include <boost/program_options.hpp>
+#include <stdexcept>
+#include <streambuf>
+#include <fstream>
+
+
+/* TODO:
+ * Support golden image properly
+ * Cleanup logging
+ * Verify inputs before starting, including that there's no overlap.
+ * Fill with 0xFF instead of 0x00
+ * Possibly intel hex file output instead?
+ * Error checking!
+ * Allow specifying inputs/addresses in any order and sort them correctly
+ * and inject golden image at the suitable position
+*/
+using namespace std;
+
+boost::optional<uint32_t> convert_hexstring(std::string value_str)
+{
+ boost::optional<uint32_t> ret;
+ uint32_t value = uint32_t(strtoul(value_str.c_str(), nullptr, 0));
+ if (value != 0)
+ ret = value;
+
+ return ret;
+}
+
+
+int main(int argc, char *argv[])
+{
+ using namespace Trellis;
+ namespace po = boost::program_options;
+
+ std::string database_folder = TRELLIS_PREFIX "/share/trellis/database";
+ uint32_t flash_size_bytes = 0;
+ boost::optional<uint32_t> input_idcode;
+ boost::optional<uint32_t> output_idcode;
+ boost::optional<uint32_t> idcode;
+ uint32_t nextaddr = 0;
+ std::array<char, 4096> fillpattern;
+
+ std::fill(fillpattern.begin(), fillpattern.end(), 0xff);
+
+ po::options_description options("Allowed options");
+ options.add_options()("help,h", "show help");
+ options.add_options()("verbose,v", "verbose output");
+ options.add_options()("db", po::value<std::string>(), "Trellis database folder location");
+ options.add_options()("input", po::value<std::vector<std::string>>()->required(), "input bitstream file 0..N");
+ options.add_options()("address", po::value<std::vector<std::string>>()->required(), "address to place next bitstream at [1..N]");
+ options.add_options()("flashsize", po::value<std::uint32_t>()->required(), "Flash size in Mbits, e.g. 2, 4, 8, ..., 128");
+ options.add_options()("input-idcode", po::value<std::string>(), "IDCODE override for input file");
+ options.add_options()("output-idcode", po::value<std::string>(), "IDCODE override in output bitstreams");
+
+ po::positional_options_description pos;
+ options.add_options()("output", po::value<std::string>()->required(), "output bitstream file");
+ pos.add("output", 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) {
+ cerr << e.what() << endl << endl;
+ goto help;
+ }
+
+ if (vm.count("help")) {
+ help:
+ cerr << "Project Trellis - Open Source Tools for ECP5 FPGAs" << endl;
+ cerr << "ecpmulti: ECP5 multiboot bitstream assembler" << endl;
+ cerr << endl;
+ cerr << "Copyright (C) 2019 Jens Andersen <jens.andersen@gmail.com>" << endl;
+ cerr << endl;
+ cerr << options << endl;
+ return vm.count("help") ? 0 : 1;
+ }
+
+ if (vm.count("flashsize")) {
+ uint32_t flash_size_mbit = vm["flashsize"].as<uint32_t>();
+ flash_size_bytes = flash_size_mbit * 1024* 1024 / 8;
+ cout << "Using flashsize " << flash_size_mbit << "(" << flash_size_bytes << " bytes)" << endl;
+ }
+
+ auto inputs = vm.at("input").as<std::vector<std::string>>();
+ auto addresses = vm.at("address").as<std::vector<std::string>>();
+
+ if (inputs.size() != (addresses.size() + 1)) {
+ cerr << "Inputs " << inputs.size() << " offsets " << addresses.size() << endl;
+ cerr << "There must be one address specified per extra bitstream (>1)" << endl;
+ return 1;
+ }
+
+ if (vm.count("input-idcode")) {
+ input_idcode = convert_hexstring(vm.at("input-idcode").as<std::string>());
+ }
+
+ if (vm.count("output-idcode")) {
+ output_idcode = convert_hexstring(vm.at("output-idcode").as<std::string>());
+ }
+
+ if (vm.count("db")) {
+ database_folder = vm["db"].as<string>();
+ }
+
+ try {
+ load_database(database_folder);
+ } catch (runtime_error &e) {
+ cerr << "Failed to load Trellis database: " << e.what() << endl;
+ return 1;
+ }
+
+ ofstream out_file(vm["output"].as<string>(), ofstream::trunc);
+ if (!out_file) {
+ cerr << "Failed to open output file" << endl;
+ return 1;
+ }
+
+ const map<string, string> bs_options = {{"multiboot", "yes"} };
+
+ for(uint32_t i = 0; i < inputs.size(); i++) {
+ ifstream bitfile;
+ string filename = inputs.at(i);
+ uint32_t address = nextaddr << 16;
+
+ if (address > flash_size_bytes || address < out_file.tellp()) {
+ cerr << "Addresses must be ordered and smaller than flash size" << endl;
+ return 1;
+ }
+
+ cout << "Processing " << filename << " for address " << hex << address << endl;
+ bitfile.open(inputs[i], ios::binary);
+
+ Bitstream bs = Bitstream::read_bit(bitfile);
+ Chip c = bs.deserialise_chip(input_idcode);
+ if (!idcode.is_initialized())
+ idcode = c.info.idcode;
+
+ if (idcode != c.info.idcode) {
+ cerr << "All input files must be for the same model (" << *idcode << " != " << c.info.idcode << ")" << endl;
+ return 1;
+ }
+
+ if (i < addresses.size()) {
+ string offset_str = addresses.at(i);
+ boost::optional<uint32_t> next_addr_val = convert_hexstring(offset_str);
+
+ if (!next_addr_val.is_initialized()) {
+ cerr << "Invalid offset: " << offset_str << endl;
+ return 1;
+ }
+ nextaddr = ((*next_addr_val) & 0x00FF0000) >> 16;
+ } else
+ /* Point back to first bitstream */
+ nextaddr = 0;
+
+ auto tile_db = get_tile_bitdata(TileLocator{c.info.family, c.info.name, "EFB1_PICB1"});
+ WordSettingBits wsb = tile_db->get_data_for_setword("BOOTADDR");
+ auto tile = c.get_tiles_by_type("EFB1_PICB1");
+ if (tile.size() != 1) {
+ cerr << "EFB1_PICB1 Frame is wrong size. Can't proceed" << endl;
+ return 1;
+ }
+
+
+ for(uint32_t j=0; j < wsb.bits.size(); j++) {
+ auto bg = wsb.bits.at(j);
+ for (auto bit : bg.bits) {
+ bool value = (nextaddr & (1 << j)) > 0;
+ tile[0]->cram.set_bit(bit.frame, bit.bit, value);
+ }
+ }
+
+ uint32_t fill_size = address - out_file.tellp();
+ while(fill_size > 0) {
+ uint32_t batch_size = std::min(fill_size, (uint32_t)4096);
+ out_file.write(fillpattern.data(), batch_size);
+ fill_size -= batch_size;
+ }
+
+ /* Pad with 256 byte 0xFF to match Diamond tools.
+ * TN1216 indicates it has to do with garbage bytes coming out of SPI Flash sometimes */
+ out_file.write(fillpattern.data(), 256);
+
+ if (output_idcode.is_initialized()) {
+ c.info.idcode = *output_idcode;
+ }
+ bs = Bitstream::serialise_chip(c, bs_options);
+ bs.write_bit(out_file);
+ }
+
+
+ /* TODO: Support golden image
+ * 1) Inject golden image into right area of final bitfile
+ * 2) Use Bitstream::generate_jump(golden_addr) to generate a Bitstream
+ * 3) At end of file, (flash_size - 0xFF) write jump bitstream
+ */
+
+ out_file.flush();
+ out_file.close();
+ return 0;
+}