/* http://neil.franklin.ch/Projects/VirtexTools/vd.c */
/*   - VirtexDump, textual dump of LUTs and BRAMdatas */
/*     and entire GCLKs/CLBs/IOBs/BRAMs/DLLs from Virtex bitstreams */
/*     presently only CLBs (LUTs and entire CLBs) implemented */
/* author Neil Franklin, last modification 2002.10.15 */


/* ------ prerequisites section */

/* to really understand this code you should */
/*   have an understanding of the basics of */
/*     designing logic circuits, such as with [74|74LS|74F]xx(x) chips */
/*     basics of how programmable logic works, particularly FPGAs */
/*   have read the specifics from the */
/*     Xilinx Virtex 2.5V FPGAs (XCV00) data sheet */
/*       http://www.xilinx.com/partinfo/ds003.pdf */
/*     Xilinx Virtex-E 1.8V FPGAs (XCV00E) data sheet */
/*       http://www.xilinx.com/partinfo/ds022.pdf */
/*     Xilinx Virtex-E 1.8V Extended Memory FPGAs (XCV00E) data sheet */
/*       http://www.xilinx.com/partinfo/ds025.pdf */
/*     Xilinx Spartan[tm]-II data sheet (XC2S00) */
/*       http://www.xilinx.com/partinfo/ds001.htm */
/*     Xilinx Spartan[tm]-IIE data sheet (XC2S00E) */
/*       http://www.xilinx.com/partinfo/ds077.htm */


/* ------ include section */

#include "virtex.h"


/* ------ prototypes section */

int main(int argc, char *argv[], char *env[]);

void getoptions(char *argv[]);
void helpoptions(int error);

void dumpoutfile(char *OutfileName, int Verbose);

void dumpclbs(void);
void dumplut(void);

void dumpentireclbs(void);
void dumpentireclb(void);

void dumpmemoryclbs(void);
void dumpmemorylutstripe(void);

void formatmemory(char *Memory[], int Words, char *Header[], int Lines,
  char *Base);

void dumpmemoryzzclbs(void);
void dumpmemoryzzlutstripe(void);


/* ------ main program control section */

char *MainProgName;

/* NULL for no file given, = use stdin */
char *MainBitfileName = NULL;

/* NULL for no file given, = use stdout */
char *MainOutfileName = NULL;

#define MainNoVerbose 0
#define MainDoVerbose 1
int MainVerbose = MainNoVerbose;

int main(int argc, char *argv[], char *env[]) {

  /* straight forward, really :-) */

  char *serror;

  getoptions(argv);

  serror = readbitfile(MainBitfileName, MainVerbose == MainDoVerbose);
  if (serror != NULL) {
    fprintf(stderr, "%s: %s\n", MainProgName, serror);
    exit(1); }

  dumpoutfile(MainOutfileName, MainVerbose == MainDoVerbose);

  exit(0); }


/* ------ get command line options section */

#define OptNoEntire 0
#define OptDoEntire 1
int OptEntire = OptNoEntire;

#define OptNoMemory 0
#define OptDoMemory 1
int OptMemory = OptNoMemory;

#define OptNoZigzag 0
#define OptDoZigzag 1
int OptZigzag = OptNoZigzag;

#define OptLongDecoration 0
#define OptShortDecoration 1
#define OptQuietDecoration 2
int OptDecoration = OptLongDecoration;

/* NULL for no range given, use default complete chip */
char *OptRange = NULL;

/* NULL for no base given, use default binary and byte=word */
char *OptMemoryBase = NULL;

void getoptions(char *argv[]) {

  MainProgName = *argv++;

  while (*argv != NULL && (*argv)[0] == '-') {
    /* it is a bunch options, modify fitting Opt* variables */

    char *option;
    /* can be -xyz multiple options */
    for (option = (*argv)+1; *option != 0; option++) {

      if (*option == 'e') {
        /* -e (entire bits) */
        OptEntire = OptDoEntire; }

      else if (*option == 'm') {
        /* -m (memory output) */
        OptMemory = OptDoMemory; }

      else if (*option == 'z') {
        /* -z (zigzag memory), implies -m (memory output) */
        OptMemory = OptDoMemory;
        OptZigzag = OptDoZigzag; }

      else if (*option == 'l') {
        /* -l (long decoration) */
        OptDecoration = OptLongDecoration; }

      else if (*option == 's') {
        /* -s (short decoration) */
        OptDecoration = OptShortDecoration; }

      else if (*option == 'q') {
        /* -q (quiet decoration) */
        OptDecoration = OptQuietDecoration; }

      else if (*option == 'v') {
        /* -v (verbose operation) */
        MainVerbose = MainDoVerbose; }

      else if (*option == 'r') {
        /* -r (range): next argument must be range description, take it  */
        argv++;
        OptRange = *argv; }

      else if (*option == 't') {
        /* -t (type): next argument must be base description, take it  */
        argv++;
        OptMemoryBase = *argv; }

      else if (*option == 'o') {
        /* -o (output set): next argument must be outfile name, take it */
        argv++;
        MainOutfileName = *argv; }

      else if (*option == 'h') {
        /* -h (help): output help option text, and abort then */
        helpoptions(0); }

      else {
        /* unknown option given, give help and abort with error */
        fprintf(stderr, "%s: Error: unknown option: -%c\n\n",
          MainProgName, *option);
        helpoptions(1); } }

    argv++; }

  if (*argv != NULL) {
    /* starting with non-digit, must be an bit file name */
    MainBitfileName = *argv++; }

  if (*argv != NULL) {
    /* there should be no params any more, give help and abort with error */
    fprintf(stderr, "%s: Error: too many parameters at: %s\n\n",
      MainProgName, *argv);
    helpoptions(1); }

  if (MainVerbose == MainDoVerbose) {
    fprintf(stderr, "*** %s: running in verbose mode ***\n", MainProgName); }

  return; }


/* ------ output help options text section */

void helpoptions(int error) {

  FILE *Output;

  if (error) {
    Output = stderr; }
  else {
    Output = stdout; }

  fprintf(Output,
    "Usage: %s [-emzlsqvh] [-r range] [-t base[byte]] [-o file] [bitfile]\n",
    MainProgName);
  fprintf(Output, "\n");
  fprintf(Output, "Options:\n");
  fprintf(Output,
    "  -e            entire element: CLB as bit pattern (def just LUTs)\n");
  fprintf(Output,
    "  -m            memory: LUTs as LUT-RAMs, rows merged and rotated\n");
  fprintf(Output,
    "  -mz or -z     mem zigzag: LUT-RAM32s bit0/2/.. 1st sli, 1/3/.. 2nd\n");
  fprintf(Output,
    "  -l            long decoration: labels/addresses/data (default)\n");
  fprintf(Output,
    "  -s            short decoration: addresses/data, no labels\n");
  fprintf(Output,
    "  -q            quiet decoration: just data, no labels/addresses\n");
  fprintf(Output,
    "  -v            verbose operation: running activity report to stderr\n");
  fprintf(Output,
    "  -h            help: display this help text, then abort\n");
  fprintf(Output,
    "  -r spec       restrict range to only: [horizontal][/vertical]\n");
  fprintf(Output,
    "                horizontal: [left][-[right]], with left<right\n");
  fprintf(Output,
    "                vertical: [bottom][-[top]], with bottom<top\n");
  fprintf(Output,
    "                left, right: column[slice];  bottom, top: row[LUT]\n");
  fprintf(Output,
    "                column, row: number 0 to size-1 (from left or bottom)\n");
  fprintf(Output,
    "                slice: L (left=1) or R (right=0);  LUT: F or G\n");
  fprintf(Output,
    "  -t base[byte] type: of base of memory output (default bI)\n");
  fprintf(Output,
    "                base: a ascii, b binary, d decimal, o octal, h hex\n");
  fprintf(Output,
    "                byte: width number 1 to n or I=word, C=8, S=16, L=32\n");
  fprintf(Output,
    "  -o file       output: send to file (default stdout)\n");
  fprintf(Output, "\n");
  fprintf(Output,
    "bitfile:        read bitstream from this file (default stdin)\n");

  /* and abort, do not run actual program, neither after -h nor after error */
  exit(error); }


/* ------ dump bits to output file section */

FILE *Outfile;

void dumpoutfile(char *OutfileName, int Verbose) {

  char *serror;
  int ierror;

  if (OutfileName) {
    if (Verbose) {
      fprintf(stderr, "dumping to outfile %s\n", OutfileName); }
    Outfile = fopen(OutfileName, "w");
    if (!Outfile) {
      fprintf(stderr, "%s: %s: %s\n",
        MainProgName, strerror(errno), OutfileName);
      exit(1); } }
  else {
    if (Verbose) {
      fprintf(stderr, "dumping to standard output\n"); }
    Outfile = stdout; }

  /* *fixme*: at present we only support dumping CLBs */

  /* select what rage we want to see dumped */
  if (Verbose) {
    fprintf(stderr, "setting range for dumping CLBs\n"); }
  serror = setclbrange(OptRange);
  if (serror != NULL) {
    fprintf(stderr, "%s: %s\n", MainProgName, serror);
    exit(1); }

  /* do the actual dumping */
  /*  select type of dumping, from more special to least special */
  if (OptMemory == OptDoMemory) {
    if (OptZigzag == OptDoZigzag) {
      if (Verbose) {
        fprintf(stderr, "dumping LUT-RAM32 data as zigzagged memory\n"); }
      dumpmemoryzzclbs(); }
    else {
      if (Verbose) {
        fprintf(stderr, "dumping LUT-RAM data as wordwise memory\n"); }
      dumpmemoryclbs(); } }
  else if (OptEntire == OptDoEntire) {
    if (Verbose) {
      fprintf(stderr, "dumping entire CLB data\n"); }
    dumpentireclbs(); }
  else {
    if (Verbose) {
      fprintf(stderr, "dumping LUT data\n"); }
    dumpclbs(); }

  ierror = fclose(Outfile);
  if (ierror) {
    fprintf(stderr, "%s: %s: %s\n",
      MainProgName, strerror(errno), OutfileName);
    exit(1); }

  if (Verbose) {
    fprintf(stderr, "finished dumping\n"); } }


/* ------ dump CLBs to outfile section */

/* grid of CLBs, call dumplut() for each LUT */

void dumpclbs(void) {

  /* *Col, *Sli, *Row and *Lut are extern global vars, from libvirtex */
  /*   First*, Last*, Begin* and End* have been set by setclbrange() */

  /* interleave halves of CLBs (Sli) with CLBs (Col) */
  for (Col = FirstCol; Col <= LastCol; Col++) {
    for (Sli = FirstSli; Sli <= LastSli; Sli++) {
      if (!(Col == FirstCol && Sli == SLIL && BeginSli == SLIR ||
            Col == LastCol  && Sli == SLIR && EndSli   == SLIL)) {
        /* interleave halves of slices (Lut) with slices (Row) */
        for (Row = FirstRow; Row <= LastRow; Row++) {
          for (Lut = FirstLut; Lut <= LastLut; Lut++) {
            if (!(Row == FirstRow && Lut == LUTF && BeginLut == LUTG ||
	          Row == LastRow  && Lut == LUTG && EndLut   == LUTF)) {
              dumplut(); } } } } } } }


/* single LUT, Col/Sli/Row/Lut values from global vars */

void dumplut(void) {

  if (OptDecoration == OptLongDecoration) {
    /* long decoration of line for each LUT pair: */
    /* Col: ccc, Sli: s, Row: rrr, LUT: l, Val: 0xvvvv */
    /* example for one (bottom left) LUT: */
    /* Col:   0, Sli: L, Row:   0, LUT: F, Val: 0x0000 */
    fprintf(Outfile, "Col: %3i, Sli: %1c, Row: %3i, LUT: %1c, Val: 0x%04x\n",
            Col, (Sli == SLIR) ? 'R' : 'L',
            Row, (Lut == LUTG) ? 'G' : 'F', getlut()); }

  else if (OptDecoration == OptShortDecoration) {
    /* short decoration of line for each LUT pair: */
    /* cccsrrrl: 0xvvvv */
    /* example for one (bottom left) LUT: */
    /* 000L000F: 0x0000 */
    fprintf(Outfile, "%03i%1c%03i%1c: 0x%04x\n",
            Col, (Sli == SLIR) ? 'R' : 'L',
            Row, (Lut == LUTG) ? 'G' : 'F', getlut()); }

  else {
    /* quiet decoration of line for each LUT pair: */
    /* 0xvvvv */
    /* example for one (bottom left) LUT: */
    /* 0x0000 */
    fprintf(Outfile, "0x%04x\n", getlut()); } }


/* ------ dump entire CLBs to outfile section */

void dumpentireclbs(void) {

  /* the "entire" mode simply ignores *Sli and *Lut variables */
  /*   as all 4 quaters of an CLB are in same CLB */
  for (Col = FirstCol; Col <= LastCol; Col++) {
    for (Row = FirstRow; Row <= LastRow; Row++) {
      dumpentireclb();
      /* if annother CLB is to be dumped, print an separating line */
      if (!(Col == LastCol && Row == LastRow)) {
        fprintf(Outfile, "\n"); } } } }


/* single CLB, as no slice/LUT at this level */

void dumpentireclb(void) {

  if (OptDecoration == OptLongDecoration) {
    /* stats (what we are dumping) */
    fprintf(Outfile, "Col: %i, Row: %i CLB:\n", Col, Row);
    /* number bit columns */
    fprintf(Outfile,
            "   0         1         2         3         4        frame\n");
    fprintf(Outfile,
            "   012345678901234567890123456789012345678901234567 addr\n");
    fprintf(Outfile,
            "  .------------------------------------------------\n"); }

  if (OptDecoration == OptShortDecoration) {
    /* short stats, just data without headers */
    fprintf(Outfile, "%i/%i:\n", Col, Row); }


  /* Bit and Frame are extern global vars, from libvirtex */
  for (Bit = 0; Bit < VirtexMiddleBits; Bit++) {

    char LineOfBits[VirtexClbFrames+1];
    memset(LineOfBits, 0, VirtexClbFrames+1);

    for (Frame = 0; Frame < VirtexClbFrames; Frame++) {
      LineOfBits[Frame] = getclb() ? '1' : '0'; }

    if (OptDecoration == OptLongDecoration) {
      /* number bit rows */
      fprintf(Outfile, "%2i|", Bit); }
    fprintf(Outfile, "%s\n", LineOfBits); }


  if (OptDecoration == OptLongDecoration) {
    /* footer which completes the header */
    fprintf(Outfile, "bit addr\n"); } }


/* ------ dump CLBs as memory to outfile section */

void dumpmemoryclbs(void) {

  /* interleave halves of CLBs (Sli) with CLBs (Col), same as dumpclbs() */
  for (Col = FirstCol; Col <= LastCol; Col++) {
    for (Sli = FirstSli; Sli <= LastSli; Sli++) {
      if (!(Col == FirstCol && Sli == SLIL && BeginSli == SLIR ||
            Col == LastCol  && Sli == SLIR && EndSli   == SLIL)) {
        dumpmemorylutstripe();
        if (!(Col == LastCol && Sli == EndSli)) {
          fprintf(Outfile, "\n"); } } } } }


/* stripe is an vertical group of LUTs */

void dumpmemorylutstripe(void) {

  int MemWidth, BitInWord = 0;
  int HeaderLoop, MemAddress;
  char *HeaderLine[4], *MemWord[16];


  /* calculate space to render memory into, memory has 2 bits per row */
  MemWidth = (LastRow-FirstRow+1)*2;
  if (FirstLut == LUTG || LastLut == LUTF) {
    /* memory hab been cut down to 1 LUT per row */
    MemWidth /= 2; }
  else {
    if (BeginLut == LUTG) {
      /* first row only 1 LUT */
      MemWidth -= 1; }
    if (EndLut == LUTF) {
      /* last row only 1 LUT */
      MemWidth -= 1; } }

  /* get space to render memory into */
  for (HeaderLoop = 0; HeaderLoop < 4; HeaderLoop++) {
    HeaderLine[HeaderLoop] = malloc(MemWidth+1);
    /* zero terminate the strings, will be filled by indexed writes */
    HeaderLine[HeaderLoop][MemWidth] = '\0'; }
  for (MemAddress = 0; MemAddress < 16; MemAddress++) {
    MemWord[MemAddress] = malloc(MemWidth+1);
    MemWord[MemAddress][MemWidth] = '\0'; }


  /* we want MSB left, so we count Row/Lut downwards */
  for (Row = LastRow; Row >= FirstRow; Row--) {
    for (Lut = LastLut; Lut >= FirstLut; Lut--) {
      if (!(Row == FirstRow && Lut == LUTF && BeginLut == LUTG ||
            Row == LastRow  && Lut == LUTG && EndLut   == LUTF)) {

        char RowString[4];
        uint LutVal;

        snprintf(RowString, sizeof(RowString), "%03i", Row);
        /* 0 = hundreds, 1 = tens, 2 = ones */
        HeaderLine[0][BitInWord] = RowString[0];
        HeaderLine[1][BitInWord] = RowString[1];
        HeaderLine[2][BitInWord] = RowString[2];
        HeaderLine[3][BitInWord] = (Lut == LUTG) ? 'G' : 'F';

        LutVal = getlut();

        /* transpose bits, from  Addr hor / Lut ver  to  Bit hor / Addr ver */
        for (MemAddress = 0; MemAddress < 16; MemAddress++) {
          char BitChar = (LutVal & (1<<MemAddress)) ? '1' : '0';
          MemWord[MemAddress][BitInWord] = BitChar; }

        BitInWord++; } } } 


  /* output formatted if user wants it, not just raw bits */
  if (OptDecoration == OptLongDecoration) {
    formatmemory(MemWord, 16, HeaderLine, 4, OptMemoryBase); }
  else {
    formatmemory(MemWord, 16, NULL, 0, OptMemoryBase); }

  if (OptDecoration == OptLongDecoration) {
    /* output stats (what we are dumping), column and slice */
    fprintf(Outfile, "Col: %i, Sli: %c, Memory Data:\n",
            Col, (Sli == SLIR) ? 'R' : 'L');
    /* output which row we are in */
    fprintf(Outfile, "   %s\n", HeaderLine[0]);
    fprintf(Outfile, "   %s\n", HeaderLine[1]);
    fprintf(Outfile, "   %s\n", HeaderLine[2]);
    /* output which LUT we are in */
    fprintf(Outfile, "   %s\n", HeaderLine[3]);
    /* output separator, full line of HeaderLine/MemWord width, reuse [0] */
    memset(HeaderLine[0], '-', strlen(HeaderLine[0]));
    fprintf(Outfile, "  .%s\n", HeaderLine[0]); }

  if (OptDecoration == OptShortDecoration) {
    /* short stats, just column/slice number and then data without headers */
    fprintf(Outfile, "%03i%1c/%03i%1c-%03i%1c:\n",
            Col,      (Sli == SLIR) ? 'R' : 'L',
            LastRow,  (LastLut == LUTG) ? 'G' : 'F',
            FirstRow, (FirstLut == LUTG) ? 'G' : 'F'); }

  for (MemAddress = 0; MemAddress < 16; MemAddress++) {
    if (OptDecoration == OptLongDecoration) {
      /* number addresses */
      fprintf(Outfile, "%2i|", MemAddress); }
    fprintf(Outfile, "%s\n", MemWord[MemAddress]); }

  for (HeaderLoop = 0; HeaderLoop < 4; HeaderLoop++) {
    free(HeaderLine[HeaderLoop]); }
  for (MemAddress = 0; MemAddress < 16; MemAddress++) {
    free(MemWord[MemAddress]); } }


/* ------ format memory output section */

void formatmemory(char *Memory[], int Words, char *Header[], int Lines,
  char *Base) {
  /* Memory must point to Words long array of strings, char 0/1 per bit */
  /* Header can point to Lines long array of strings, one char per bit */
  char FormatChar, FormatString[10];
  int BitsPerDigit, MemWidth, ByteLength, DigitsPerByte;
  int Word, Line;

  /* if user wants memory data unformatted return unchanged, else format */
  if (Base == NULL) {
    return; }


  /* if an "od" user types c, convert him to a, we have no special c case */
  if      (*Base == 'c') { *Base = 'a'; }
  /* if an "od" user types u, convert him to d, d is already unsigned */
  else if (*Base == 'u') { *Base = 'd'; }
  /* for h we use x internally, but externally h is more obvious for hex */
  else if (*Base == 'h') { *Base = 'x'; }

  if      (*Base == 'a') { FormatChar = *Base++; BitsPerDigit = 8; }
  else if (*Base == 'b') { FormatChar = *Base++; BitsPerDigit = 1; }
  else if (*Base == 'd') { FormatChar = *Base++; BitsPerDigit = 0; }
  else if (*Base == 'o') { FormatChar = *Base++; BitsPerDigit = 3; }
  else if (*Base == 'x') { FormatChar = *Base++; BitsPerDigit = 4; }
  /* default base to 'b', as this is like if no formatting at all */
  else                   { FormatChar = 'b'; BitsPerDigit = 1; }


  /* default byte length to memory width, as is like if no formatting at all */
  ByteLength = MemWidth = strlen(Memory[0]);
  /* char format usually used to view bytes as characters, default 8bit/char */
  if      (FormatChar == 'a') { ByteLength = 8; }

  if      (*Base == 'C') { ByteLength = 8;        Base++; }
  else if (*Base == 'I') { ByteLength = MemWidth; Base++; }
  else if (*Base == 'L') { ByteLength = 32;       Base++; }
  else if (*Base == 'S') { ByteLength = 16;       Base++; }
  else if (isdigit(*Base)) {
    ByteLength = (int)strtoul(Base, &Base, 10); }

  if (*Base != 0) {
    fprintf(stderr, "%s: Error: unknown char in base type at: %s\n\n",
      MainProgName, Base);
    exit(1); }

  if (ByteLength > MemWidth) {
    fprintf(stderr, "%s: Error: byte wider than word is senseless: %i>%i\n",
      MainProgName, ByteLength, MemWidth);
    exit(1); }

  if (FormatChar == 'a' && ByteLength < 7) {
    fprintf(stderr, "%s: Error: byte too short to contain ASCII: %i<7\n",
      MainProgName, ByteLength);
    exit(1); }


  /* calc how much chars space we need for an byte */
  if (FormatChar == 'a') {
    /* characters shown 0..31+127 3 char ASCII name, rest char and 2 blank */
    DigitsPerByte = 3; }
  else if (FormatChar == 'd') {
    /* for decimal we can only use an table lookup */
    /*   it would be add 1 every ln10/ln2 = 3.3219 steps */
    /*   no I am not calculating ln10/ln2 in integer, *shudder* */
    /*   table for 1-32 bit wide bytes, larger are not converted anyway */
    int DecimalDigitsPerByte[] = { 1,1,1,2,2,2,3,3, 3,4,4,4,4,5,5,5,
                                   6,6,6,7,7,7,7,8, 8,8,9,9,9,10,10,10 };
    DigitsPerByte = DecimalDigitsPerByte[ByteLength-1]; }
  else {
    DigitsPerByte = ByteLength/BitsPerDigit;
    /* no exact fit, % rounds downwards, we want upwards */
    if (DigitsPerByte*BitsPerDigit < ByteLength) {
      DigitsPerByte++; } }
  sprintf(FormatString, "%%0%i%c", DigitsPerByte, FormatChar);


  for (Word = 0; Word < Words; Word++) {
    char *From = Memory[Word], Buffer[1024], *To = Buffer;

    /* separate bytes with spaces */
    while (From+ByteLength < Memory[Word]+MemWidth) {
      strncpy(To, From, ByteLength);
      From += ByteLength; To += ByteLength;
      *To++ = ' '; }
    if (From < Memory[Word]+MemWidth) {
      /* remaining bits, less than ByteLength */
      strncpy(To, From, Memory[Word]+MemWidth-From);
      To += Memory[Word]+MemWidth-From; }
    else {
      /* undo blank after last byte */
      To--; }
    *To = 0;

    /* realloc Memory[] to proper size and copy Buffer to it */
    Memory[Word] = realloc(Memory[Word], strlen(Buffer)+1);
    strcpy(Memory[Word], Buffer);


    /* convert binary number to other forms */
    if (FormatChar != 'b') {
      /* use of sprintf and "ulong" sets limit, alternative lots of code */
      if (ByteLength > 32) {
        fprintf(stderr, "%s: Error: byte wider 32(long) not convertable: %i\n",
          MainProgName, ByteLength);
        exit(1); }
      From = Memory[Word]; To = Buffer;
      while (From < Memory[Word]+MemWidth) {
        ulong Byte = strtoul(From, &From, 2);
        if (FormatChar == 'a') {
          /* only one character/byte, multi lot of work, unlikely to be used */
          Byte &= 0xFF;
          /* control chars converted to names, else they kill terminal */
          if      ((Byte&0x7F) < 32) {
            char *Ctrl[] = { "NUL","SOH","STX","ETX","EOT","ENQ","ACK","BEL",
                             "BS ","HT ","LF ","VT ","FF ","CR ","SO ","SI ",
                             "DLE","DC1","DC2","DC3","DC4","NAK","SYM","ETB",
                             "CAN","EM ","SUB","ESC","FS ","GS ","RS ","US " };
            To += sprintf(To, "%3s", Ctrl[Byte&0x7F]); }
          else if ((Byte&0x7F) == 0x7F) {
            To += sprintf(To, "DEL");  }
          else {
            To += sprintf(To, "%c  ", Byte); } }
        else {
          To += sprintf(To, FormatString, Byte); }
        if (*From == ' ') {
          *To++ = *From++; } }
      *To = 0;

      Memory[Word] = realloc(Memory[Word], strlen(Buffer)+1);
      strcpy(Memory[Word], Buffer); } }


  /* no header to format, abort rest of formatmemory() */
  if (Header == NULL || Lines == 0) {
    return; }

  for (Line = 0; Line < Lines; Line++) {
    char *From = Header[Line], Buffer[1024], *To = Buffer;

    /* header insert identical spaces between bytes, copy rest */
    while (From+ByteLength < Header[Line]+MemWidth) {
      strncpy(To, From, ByteLength);
      From += ByteLength; To += ByteLength;
      *To++ = ' '; }
    if (From < Header[Line]+MemWidth) {
      strncpy(To, From, Header[Line]+MemWidth-From);
      To += Header[Line]+MemWidth-From; }
    else {
      To--; }
    *To = 0;

    Header[Line] = realloc(Header[Line], strlen(Buffer)+1);
    strcpy(Header[Line], Buffer);


    /* header, copy header of 1st bit of bitgroup that makes one char */
    if (FormatChar != 'b') {
      From = Header[Line]; To = Buffer;
      while (From < Header[Line]+MemWidth) {
        int InsertBlank;
        /* one header column per byte, better would be one per digit */
        /*   but that would be a lot of work, because of byte<n*digit */
        *To++ = *From; From += ByteLength;
        for (InsertBlank = 0; InsertBlank < DigitsPerByte-1; InsertBlank++) {
          *To++ = ' '; }
        /* make sure we did not overrun the memory word width */
        if (From < Header[Line]+MemWidth && *From == ' ') {
          *To++ = *From++; } }
      *To = 0;

      /* realloc Header[] to proper size and copy Buffer to it */
      Header[Line] = realloc(Header[Line], strlen(Buffer)+1);
      strcpy(Header[Line], Buffer); } } }


/* ------ dump group of CLBs as zigzagged memory to outfile section */

void dumpmemoryzzclbs(void) {

  /* we always have pairs of slices, one for 0/2/.., one for 1/3/.. */
  /*   so we call with the first Col/Sli of an pair, so no  for (Sli...)  */
  for (Col = FirstCol; Col <= LastCol; Col++) {
    dumpmemoryzzlutstripe();
    if (!(Col == LastCol)) {
      fprintf(Outfile, "\n"); } } }


/* stripe is here 2 neighbor vertical groups of LUTs, for zigzagging */

void dumpmemoryzzlutstripe(void) {

  int MemWidth, BitInWord = 0;
  int HeaderLoop, MemAddress;
  char *HeaderLine[3], *MemWord[32];


  /* zigzaged memory has 1 bits per slice and 2 slices parallel, 2 per row */
  MemWidth = (LastRow-FirstRow+1)*2;

  for (HeaderLoop = 0; HeaderLoop < 3; HeaderLoop++) {
    HeaderLine[HeaderLoop] = malloc(MemWidth+1);
    /* zero terminate the strings, will be filled by indexed writes */
    HeaderLine[HeaderLoop][MemWidth] = '\0'; }
  for (MemAddress = 0; MemAddress < 32; MemAddress++) {
    MemWord[MemAddress] = malloc(MemWidth+1);
    MemWord[MemAddress][MemWidth] = '\0'; }


  /* we want MSB left, so we count Row downwards */
  /* we requre 2 LUTs per bit, must be an F/G pair, so no for (Lut...) */
  for (Row = LastRow; Row >= FirstRow; Row--) {

    char RowString[4];
    uint LutValF, LutValG;

    /* 2 slices gives 2 pairs of LUTs as 2 32bit memories */

    snprintf(RowString, sizeof(RowString), "%03i", Row);
    /* 0 = hundreds, 1 = tens, 2 = ones */
    HeaderLine[0][BitInWord] = RowString[0];
    HeaderLine[1][BitInWord] = RowString[1];
    HeaderLine[2][BitInWord] = RowString[2];

    /* bits n-1/../3/1 come from second Col/Sli, do/undo the zigzag */

    Sli = (Sli == SLIL) ? SLIR : SLIL;
    if (Sli == SLIL) { Col += 1; }

    Lut = LUTF; LutValF = getlut();
    Lut = LUTG; LutValG = getlut();

    Sli = (Sli == SLIL) ? SLIR : SLIL;
    if (Sli == SLIR) { Col += 1; }

    /* transpose LUT bits, from  Addr hor / Lut ver  to  Bit hor / Addr ver */
    /* first 16 addresses come from F LUTs */
    for (MemAddress = 0; MemAddress < 16; MemAddress++) {
      char BitChar = (LutValF & (1<<MemAddress)) ? '1' : '0';
      MemWord[MemAddress][BitInWord] = BitChar; }
    /* second 16 addresses come from G LUTs */
    for (MemAddress = 0; MemAddress < 16; MemAddress++) {
      char BitChar = (LutValG & (1<<MemAddress)) ? '1' : '0';
      MemWord[MemAddress+16][BitInWord] = BitChar; }

    BitInWord++;


    HeaderLine[0][BitInWord] = RowString[0];
    HeaderLine[1][BitInWord] = RowString[1];
    HeaderLine[2][BitInWord] = RowString[2];

    /* bits n-2/../2/0 come from Col/Sli, no zigzag */

    Lut = LUTF; LutValF = getlut();
    Lut = LUTG; LutValG = getlut();

    /* transpose LUT bits, from  Addr hor / Lut ver  to  Bit hor / Addr ver */
    /* first 16 addresses come from F LUTs */
    for (MemAddress = 0; MemAddress < 16; MemAddress++) {
      char BitChar = (LutValF & (1<<MemAddress)) ? '1' : '0';
      MemWord[MemAddress][BitInWord] = BitChar; }
    /* second 16 addresses come from G LUTs */
    for (MemAddress = 0; MemAddress < 16; MemAddress++) {
      char BitChar = (LutValG & (1<<MemAddress)) ? '1' : '0';
      MemWord[MemAddress+16][BitInWord] = BitChar; }

    BitInWord++; }


  /* output formatted if user wants it, not just raw bits */
  if (OptDecoration == OptLongDecoration) {
    formatmemory(MemWord, 32, HeaderLine, 3, OptMemoryBase); }
  else {
    formatmemory(MemWord, 32, NULL, 0, OptMemoryBase); }

  if (OptDecoration == OptLongDecoration) {
    /* output stats (what we are dumping), column and slice */
    int SecondCol = (Sli == SLIL) ? Col : Col+1;
    int SecondSli = (Sli == SLIL) ? SLIR : SLIL;
    fprintf(Outfile, "Col/Sli: %i%c and %i%c, Memory Data:\n",
            Col,       (Sli == SLIR) ? 'R' : 'L',
            SecondCol, (SecondSli == SLIR) ? 'R' : 'L');
    /* output which row we are in */
    fprintf(Outfile, "    %s\n", HeaderLine[0]);
    fprintf(Outfile, "    %s\n", HeaderLine[1]);
    fprintf(Outfile, "    %s\n", HeaderLine[2]);
    /* output separator, full line of HeaderLine/MemWord width, reuse [0] */
    memset(HeaderLine[0], '-', strlen(HeaderLine[0]));
    fprintf(Outfile, "   .%s\n", HeaderLine[0]); }

  if (OptDecoration == OptShortDecoration) {
    /* short stats, just column/slice number and then data without headers */
    int SecondCol = (Sli == SLIL) ? Col : Col+1;
    int SecondSli = (Sli == SLIL) ? SLIR : SLIL;
    fprintf(Outfile, "%03i%1c+%03i%1c/%03i-%03i:\n",
            Col,       (Sli == SLIR) ? 'R' : 'L',
            SecondCol, (SecondSli == SLIR) ? 'R' : 'L',
            LastRow,   FirstRow); }

  for (MemAddress = 0; MemAddress < 32; MemAddress++) {
    if (OptDecoration == OptLongDecoration) {
      /* number addresses */
      fprintf(Outfile, "%c%2i|", (MemAddress<16) ? 'F' : 'G', MemAddress); }
    fprintf(Outfile, "%s\n", MemWord[MemAddress]); }

  for (HeaderLoop = 0; HeaderLoop < 3; HeaderLoop++) {
    free(HeaderLine[HeaderLoop]); }
  for (MemAddress = 0; MemAddress < 32; MemAddress++) {
    free(MemWord[MemAddress]); } }

