From ff02cd753c5802c25f770a788eba329ddb668d13 Mon Sep 17 00:00:00 2001 From: Clifford Wolf Date: Sun, 8 Jan 2017 13:09:09 +0100 Subject: Added icecompr --- icecompr/.gitignore | 5 + icecompr/Makefile | 31 +++++ icecompr/README | 52 ++++++++ icecompr/example_1k.bin | Bin 0 -> 32220 bytes icecompr/example_8k.bin | Bin 0 -> 135100 bytes icecompr/icecompr.cc | 316 ++++++++++++++++++++++++++++++++++++++++++++++++ icecompr/iceuncompr.c | 162 +++++++++++++++++++++++++ 7 files changed, 566 insertions(+) create mode 100644 icecompr/.gitignore create mode 100644 icecompr/Makefile create mode 100644 icecompr/README create mode 100644 icecompr/example_1k.bin create mode 100644 icecompr/example_8k.bin create mode 100644 icecompr/icecompr.cc create mode 100644 icecompr/iceuncompr.c diff --git a/icecompr/.gitignore b/icecompr/.gitignore new file mode 100644 index 0000000..1ebf48a --- /dev/null +++ b/icecompr/.gitignore @@ -0,0 +1,5 @@ +icecompr +iceuncompr +example_?k.compr +example_?k.ok +example_?k.uncompr diff --git a/icecompr/Makefile b/icecompr/Makefile new file mode 100644 index 0000000..88512f0 --- /dev/null +++ b/icecompr/Makefile @@ -0,0 +1,31 @@ + +all: icecompr iceuncompr + +test: example_1k.ok example_8k.ok + +icecompr: icecompr.cc + clang++ -o icecompr -Wall -Wextra -std=c++11 icecompr.cc + +iceuncompr: iceuncompr.c + clang -o iceuncompr -Wall -Wextra iceuncompr.c + +%.compr: %.bin icecompr + ./icecompr -v $< $@ + + +%.uncompr: %.compr iceuncompr + ./iceuncompr $< $@ + +%.ok: %.uncompr %.bin + cmp $^ + touch $@ + +clean: + rm -f icecompr iceuncompr + rm -f example_1k.compr example_8k.compr + rm -f example_1k.uncompr example_8k.uncompr + rm -f example_1k.ok example_8k.ok + +.SECONDARY: +.PHONY: all test clean + diff --git a/icecompr/README b/icecompr/README new file mode 100644 index 0000000..5e04bb5 --- /dev/null +++ b/icecompr/README @@ -0,0 +1,52 @@ + +A simple compression algorithm for iCE40 bit-streams +==================================================== + +This directory contains tools for compressing and uncompressing +iCE40 bit-streams. The motivation is to reduce the bandwidth +requirements for bit-stream upload. + +Note that iCE40 FPGAs can not uncompress this compressed bit-streams! +Uncompression must be performed by e.g. a uC on the FPGA board. + +This compression algorithm uses the fact that most bits in an iCE40 +bit-stream are cleared. + +The bit-stream is encoded as the distances between set bits (i.e. +the length of runs of ZERO bits between two ONE bits). This sequence +of integers is stored using a simple prefix code to spend fewer bits +on smaller (more frequent) numbers. + +The algorithm includes an escape-mechanism to mix uncompressed binary +data with compressed bit-streams. This is useful when the bit-stream +contains sections that do not compress well with this algorithm, for +example in BRAM initialization data. + +The compressed bitstream starts with the ASCII string "ICECOMPR", i.e. +the hex values 0x49434543 and 0x4f4d5052 (stored as big-endian numbers). + +After the 8 bytes magic the compressed bitstream is a stream of the +following opcodes that must be interpreted by the decompressor. The +notation ZERO and ONE is used for zero and one bits. The notation foo[N] +is used for an N bit value "foo", transmitted MSB first. + + ONE count[2] + ZERO ONE count[5] + ZERO ZERO ONE count[8] + ZERO ZERO ZERO ZERO ONE count[23] + output count ZERO bits followed by a single ONE bit + + ZERO ZERO ZERO ONE count[6] data[count] + output the count data bits followed by a single ONE bit + + ZERO ZERO ZERO ZERO ZERO count[23] + output count ZERO bits and stop decompressing. (end of file) + +The program "icecompr" (C++11, ISC license) contains an implementation of a +compressor and a decompressor. The decompressor in this program however is +only used for integrity checking the compressed bit-stream. + +The program "iceuncompr" (plain C, public domain) contains an implementation +of a stand-alone decompressor. Simply copy&paste this implementation into +your uC firmware. + diff --git a/icecompr/example_1k.bin b/icecompr/example_1k.bin new file mode 100644 index 0000000..0321b9a Binary files /dev/null and b/icecompr/example_1k.bin differ diff --git a/icecompr/example_8k.bin b/icecompr/example_8k.bin new file mode 100644 index 0000000..8b6c7c5 Binary files /dev/null and b/icecompr/example_8k.bin differ diff --git a/icecompr/icecompr.cc b/icecompr/icecompr.cc new file mode 100644 index 0000000..322de9a --- /dev/null +++ b/icecompr/icecompr.cc @@ -0,0 +1,316 @@ +/* + * IceCompr -- A simple compressor for iCE40 bit-streams + * + * Copyright (C) 2017 Clifford Wolf + * + * 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 +#include +#include +#include +#include +#include +#include + +int verbose = 0; + +static void push_int_bits(std::vector &outbits, int value, int bits) +{ + while (bits-- > 0) + outbits.push_back((value >> bits) & 1); +} + +static void push_zero_bits(std::vector &outbits, int bits) +{ + while (bits-- > 0) + outbits.push_back(false); +} + +static int decode_int_from_bits(const std::vector &inbits, int &cursor, int bits) +{ + int ret = 0; + while (bits-- > 0) + if (inbits.at(cursor++)) + ret |= 1 << bits; + return ret; +} + +void ice_compress(std::vector &outbits, const std::vector &inbits) +{ + int opcode_stats_d4 = 0; + int opcode_stats_d32 = 0; + int opcode_stats_d256 = 0; + int opcode_stats_raw = 0; + int opcode_stats_d8M = 0; + int opcode_stats_end = 0; + + std::vector deltas; + int numzeros = 0; + + for (auto bit : inbits) + { + if (bit) { + deltas.push_back(numzeros); + numzeros = 0; + } else { + numzeros++; + } + } + + for (int i = 0; i < int(deltas.size()); i++) + { + int raw_len = 0; + int compr_len = 0; + int best_compr_raw_diff = -1; + int best_compr_raw_idx = -1; + int best_compr_raw_len = -1; + + for (int j = 0; j+i < int(deltas.size()); j++) + { + int delta = deltas.at(i + j); + raw_len += delta + 1; + + if (delta < 4) + compr_len += 3; + else if (delta < 32) + compr_len += 7; + else if (delta < 256) + compr_len += 11; + else + compr_len += 26; + + if (compr_len - raw_len < std::max(best_compr_raw_diff - 4, 0) || raw_len > 64) + break; + + if (compr_len - raw_len > best_compr_raw_diff) { + best_compr_raw_diff = compr_len - raw_len; + best_compr_raw_idx = j; + best_compr_raw_len = raw_len; + } + } + + if (best_compr_raw_diff > 9) + { + opcode_stats_raw++; + outbits.push_back(false); + outbits.push_back(false); + outbits.push_back(false); + outbits.push_back(true); + push_int_bits(outbits, best_compr_raw_len-1, 6); + + for (int j = 0; j <= best_compr_raw_idx; j++) { + int delta = deltas.at(i + j); + for (int k = 0; k < delta; k++) + outbits.push_back(false); + if (j < best_compr_raw_idx) + outbits.push_back(true); + } + + i += best_compr_raw_idx; + continue; + } + + int delta = deltas.at(i); + + if (delta < 4) { + opcode_stats_d4++; + outbits.push_back(true); + push_int_bits(outbits, delta, 2); + } else + if (delta < 32) { + opcode_stats_d32++; + outbits.push_back(false); + outbits.push_back(true); + push_int_bits(outbits, delta, 5); + } else + if (delta < 256) { + opcode_stats_d256++; + outbits.push_back(false); + outbits.push_back(false); + outbits.push_back(true); + push_int_bits(outbits, delta, 8); + } else { + opcode_stats_d8M++; + outbits.push_back(false); + outbits.push_back(false); + outbits.push_back(false); + outbits.push_back(false); + outbits.push_back(true); + push_int_bits(outbits, delta, 23); + } + } + + opcode_stats_end++; + outbits.push_back(false); + outbits.push_back(false); + outbits.push_back(false); + outbits.push_back(false); + outbits.push_back(false); + push_int_bits(outbits, numzeros, 23); + + if (verbose > 1) { + fprintf(stderr, "opcode d4 %5d\n", opcode_stats_d4); + fprintf(stderr, "opcode d32 %5d\n", opcode_stats_d32); + fprintf(stderr, "opcode d256 %5d\n", opcode_stats_d256); + fprintf(stderr, "opcode raw %5d\n", opcode_stats_raw); + fprintf(stderr, "opcode d8M %5d\n", opcode_stats_d8M); + fprintf(stderr, "opcode end %5d\n", opcode_stats_end); + } +} + +void ice_uncompress(std::vector &outbits, const std::vector &inbits) +{ + int cursor = 0; + + while (cursor < int(inbits.size())) + { + if (inbits.at(cursor++)) { + int zeros = decode_int_from_bits(inbits, cursor, 2); + push_zero_bits(outbits, zeros); + outbits.push_back(true); + } else + if (inbits.at(cursor++)) { + int zeros = decode_int_from_bits(inbits, cursor, 5); + push_zero_bits(outbits, zeros); + outbits.push_back(true); + } else + if (inbits.at(cursor++)) { + int zeros = decode_int_from_bits(inbits, cursor, 8); + push_zero_bits(outbits, zeros); + outbits.push_back(true); + } else + if (inbits.at(cursor++)) { + int raw_len = decode_int_from_bits(inbits, cursor, 6); + while (raw_len--) + outbits.push_back(inbits.at(cursor++)); + outbits.push_back(true); + } else + if (inbits.at(cursor++)) { + int zeros = decode_int_from_bits(inbits, cursor, 23); + push_zero_bits(outbits, zeros); + outbits.push_back(true); + } else { + int zeros = decode_int_from_bits(inbits, cursor, 23); + push_zero_bits(outbits, zeros); + } + } +} + +void help() +{ + printf("\n"); + printf("Usage: icecompr [-v] [input-file [output-file]]\n"); + printf("\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + FILE *input_file = stdin; + FILE *output_file = stdout; + + int opt; + while ((opt = getopt(argc, argv, "v")) != -1) + { + switch (opt) + { + case 'v': + verbose++; + break; + default: + help(); + } + } + + if (optind < argc) { + input_file = fopen(argv[optind], "rb"); + if (input_file == NULL) { + fprintf(stderr, "Failed to open input file `%s': %s\n", argv[optind], strerror(errno)); + return 1; + } + optind++; + } + + if (optind < argc) { + output_file = fopen(argv[optind], "wb"); + if (output_file == NULL) { + fprintf(stderr, "Failed to open output file `%s': %s\n", argv[optind], strerror(errno)); + return 1; + } + optind++; + } + + if (optind != argc) + help(); + + std::vector original_bits; + int count_set_bits = 0; + + while (1) + { + int byte = fgetc(input_file); + + if (byte < 0) + break; + + // MSB first + for (int i = 7; i >= 0; i--) { + bool bit = (byte >> i) & 1; + if (bit) count_set_bits++; + original_bits.push_back(bit); + } + } + + int uncompressed_size = original_bits.size(); + + if (verbose > 0) { + fprintf(stderr, "Percentage of set bits: %.2f%%\n", (100.0*count_set_bits) / uncompressed_size); + fprintf(stderr, "Uncompressed size: %8d bits\n", uncompressed_size); + } + + std::vector compressed_bits; + ice_compress(compressed_bits, original_bits); + + int compressed_size = compressed_bits.size(); + + if (verbose > 0) { + fprintf(stderr, "Compressed size: %8d bits\n", compressed_size); + fprintf(stderr, "Space savings: %.2f%%\n", 100 - (100.0*compressed_size) / uncompressed_size); + } + + std::vector uncompressed_bits; + ice_uncompress(uncompressed_bits, compressed_bits); + + bool check_ok = original_bits == uncompressed_bits; + + if (verbose > 0 || !check_ok) { + fprintf(stderr, "Integrity check: %s\n", check_ok ? "OK" : "ERROR"); + if (!check_ok) + return 1; + } + + fprintf(output_file, "ICECOMPR"); + for (int i = 0; i < int(compressed_bits.size()); i += 8) { + int value = 0; + for (int j = 0; j < 8 && i+j < int(compressed_bits.size()); j++) + if (compressed_bits.at(i+j)) + value |= 1 << (7-j); + fputc(value, output_file); + } + + return 0; +} + diff --git a/icecompr/iceuncompr.c b/icecompr/iceuncompr.c new file mode 100644 index 0000000..04e7dc8 --- /dev/null +++ b/icecompr/iceuncompr.c @@ -0,0 +1,162 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. + +#include +#include +#include +#include +#include + +FILE *input_file; +FILE *output_file; + +int read_bitcounter; +int read_buffer; + +int write_bitcounter; +int write_buffer; + +static int read_bit() +{ + if (read_bitcounter == 0) { + read_bitcounter = 8; + read_buffer = fgetc(input_file); + } + + read_bitcounter--; + return (read_buffer >> read_bitcounter) & 1; +} + +static void write_bit(int value) +{ + write_bitcounter--; + + if (value) + write_buffer |= 1 << write_bitcounter; + + if (write_bitcounter == 0) { + fputc(write_buffer, output_file); + write_bitcounter = 8; + write_buffer = 0; + } +} + +static int read_int(int bits) +{ + int ret = 0; + while (bits-- > 0) + if (read_bit()) + ret |= 1 << bits; + return ret; +} + +static void write_zeros(int bits) +{ + while (bits-- > 0) + write_bit(0); +} + +int ice_uncompress() +{ + read_bitcounter = 0; + read_buffer = 0; + + write_bitcounter = 8; + write_buffer = 0; + + int magic1_ok = read_int(32) == 0x49434543; + int magic2_ok = read_int(32) == 0x4f4d5052; + + if (!magic1_ok || !magic2_ok) { + fprintf(stderr, "Missing ICECOMPR magic. Abort!\n"); + return 1; + } + + while (1) + { + if (read_bit()) { + write_zeros(read_int(2)); + write_bit(1); + } else + if (read_bit()) { + write_zeros(read_int(5)); + write_bit(1); + } else + if (read_bit()) { + write_zeros(read_int(8)); + write_bit(1); + } else + if (read_bit()) { + int n = read_int(6); + while (n--) + write_bit(read_bit()); + write_bit(1); + } else + if (read_bit()) { + write_zeros(read_int(23)); + write_bit(1); + } else { + write_zeros(read_int(23)); + break; + } + } + + return 0; +} + +void help() +{ + printf("\n"); + printf("Usage: iceuncompr [input-file [output-file]]\n"); + printf("\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + input_file = stdin; + output_file = stdout; + + int opt; + while ((opt = getopt(argc, argv, "v")) != -1) + { + switch (opt) + { + case 'v': + break; + default: + help(); + } + } + + if (optind < argc) { + input_file = fopen(argv[optind], "rb"); + if (input_file == NULL) { + fprintf(stderr, "Failed to open input file `%s': %s\n", argv[optind], strerror(errno)); + return 1; + } + optind++; + } + + if (optind < argc) { + output_file = fopen(argv[optind], "wb"); + if (output_file == NULL) { + fprintf(stderr, "Failed to open output file `%s': %s\n", argv[optind], strerror(errno)); + return 1; + } + optind++; + } + + if (optind != argc) + help(); + + if (optind != argc) + help(); + + return ice_uncompress(); +} + -- cgit v1.2.3