/* convert a xilinx .bit file into a intel-hex data file
 * Copyright (c) 2000, PJRC.COM, LLC
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *
 * For general purpose conversion of xilinx bitstreams, you should
 * use the xilinx prom file formatter utility.  This program doesn't
 * support multiple bitstreams, and the data must all be within a
 * single 64k page of memory, as only a single 02 record type is
 * written at the beginning of the output.  The only real advantage
 * of this code over xilinx's utility is that its MUCH faster and
 * doesn't have an annoying splash screen that locks the GUI! 
 *
 * This code has only been tested with a XCS10XL bitstream, and it's
 * based on a bit of guesswork about the undocumented format of the
 * xilinx .bit file.  If you use this in your own project, please
 * at least verify its output against the prom file formatter.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int bitflip(unsigned char c);
void hexout(int byte, int memory_location, int end);

int main(int argc, char **argv)
{
	unsigned char buf[0x10000];
	FILE *fp;
	int i, len, begin, addr, r, addr_h, sum;

	if (argc < 3) {
		fprintf(stderr, "Usage: bit2mcs <addr> <file.bit>\n");
		fprintf(stderr, "   Ex: bit2mcs 0x1D000 mp3.bit > mp3.mcs\n");
		exit(1);
	}
	r = sscanf(argv[1], "0x%x", &addr);
	if (r != 1 || addr < 0 || addr > 0xFD000) {
		fprintf(stderr, "Illegal start address \"%s\"\n\n", argv[1]);
		exit(1);
	}

	fp = fopen(argv[2], "rb");
	if (fp == NULL) {
		fprintf(stderr, "Unable to read %s\n\n", argv[2]);
		exit(1);
	}
	len = fread(buf, 1, sizeof(buf), fp);
	if (len < 2) {
		fprintf(stderr, "%s contains no data\n\n", argv[2]);
		fclose(fp);
		exit(1);
	}

	for (begin=0; begin<len-1; begin++) {
		if (buf[begin] == 0xFF && buf[begin+1] == 0x20) break;
	}
	if (begin >= len - 1) {
		fprintf(stderr, "couldn't find bitstream within %s\n\n",
			argv[2]);
		fclose(fp);
		exit(1);
	}

	addr_h = (addr >> 12) & 0xF0;
	sum = 256 - 4 - addr_h;
	addr &= 0xFFFF;
	printf(":02000002%02X00%02X\n", addr_h, sum);

	for (i=begin; i<len; i++) {
		hexout(bitflip(buf[i]), addr++, 0);
	}
	hexout(0, addr, 1);

	fclose(fp);

	return 0;
}


#define MAXHEXLINE 16   /* the maximum number of bytes to put in one line */

/* hexout, general purpose intel hex file output... a bit overkill */
/* for this project, but easier to copy-n-paste than reinvent the wheel */
/* http://www.pjrc.com/tech/8051/pm2_docs/intel-hex.html */

void hexout(int byte, int memory_location, int end)
{
        static int byte_buffer[MAXHEXLINE];
        static int last_mem, buffer_pos, buffer_addr;
        static int writing_in_progress=0;
        register int i, sum;

        if (!writing_in_progress) {
                /* initial condition setup */
                last_mem = memory_location-1;
                buffer_pos = 0;
                buffer_addr = memory_location;
                writing_in_progress = 1;
                }

        if ( (memory_location != (last_mem+1)) || (buffer_pos >= MAXHEXLINE) \
         || ((end) && (buffer_pos > 0)) ) {
                /* it's time to dump the buffer to a line in the file */
                printf(":%02X%04X00", buffer_pos, buffer_addr);
                sum = buffer_pos + ((buffer_addr>>8)&255) + (buffer_addr&255);
                for (i=0; i < buffer_pos; i++) {
                        printf("%02X", byte_buffer[i]&255);
                        sum += byte_buffer[i]&255;
                }
                printf("%02X\n", (-sum)&255);
                buffer_addr = memory_location;
                buffer_pos = 0;
        }

        if (end) {
                printf(":00000001FF\n");  /* end of file marker */
                writing_in_progress = 0;
        }
                
        last_mem = memory_location;
        byte_buffer[buffer_pos] = byte & 255;
        buffer_pos++;
}

/* again, not very efficient... I think I've got a lookup */
/* table in another project somewhere... but this works ok */

int bitflip(unsigned char c)
{
	int i=0;

	if (c & 0x01) i |= 0x80;
	if (c & 0x02) i |= 0x40;
	if (c & 0x04) i |= 0x20;
	if (c & 0x08) i |= 0x10;
	if (c & 0x10) i |= 0x08;
	if (c & 0x20) i |= 0x04;
	if (c & 0x40) i |= 0x02;
	if (c & 0x80) i |= 0x01;
	return i;
}


