#include <errno.h>
#include <getopt.h>
-#define VERSION 1
+#define VERSION 2
void
-print_usage( char ** argv )
+print_usage(char ** argv)
{
printf( "bin2load v%d (www.subgeniuskitty.com)\n"
- "Usage: %s i <file> -o <file> [-a <address>]\n"
- " -i <file> Binary file to be written to tape.\n"
- " For example, binary executable from 'pdp11-aout-objdump'.\n"
- " -o <file> New paper tape image for use with SIMH.\n"
- " -a <address> Address on PDP-11 at which to load paper tape contents.\n"
+ "Usage: %s -i <file> -o <file> [-a <address>]\n"
+ " -i <file> Raw binary file to be written to tape.\n"
+ " For example, output from 'pdp11-aout-objdump' (see README.md).\n"
+ " -o <file> Output file created by bin2load containing tape image for use with SIMH.\n"
+ " -a <address> (optional) Address on PDP-11 at which to load tape contents.\n"
, VERSION, argv[0]
);
}
-
int
-main( int argc, char ** argv)
+main(int argc, char ** argv)
{
int c;
FILE * src = NULL;
FILE * dst = NULL;
- uint16_t address = 01000; /* Default address to load tape contents. */
+ uint16_t address = 01000; /* Default address to load tape contents in RAM. */
while ((c = getopt(argc, argv, "i:o:a:h")) != -1) {
switch (c) {
print_usage(argv);
exit(EXIT_FAILURE);
}
+
printf("Paper tape will load at address 0%o.\n", address);
/*
- * Paper tape format(bytes, not words):
- * 01
- * 00
- * Low byte of packet length (binary length + 6 for header)
- * High byte of packet length
- * Low byte of address to load binary data at
- * High byte of address
- * DataBegin
- * |
- * |
- * DataEnd
- * Checksum
+ * SIMH Binary Loader Format
+ *
+ * Loader format consists of blocks, optionally preceded, separated, and
+ * followed by zeroes. Each block consists of the following entries. Note
+ * that all entries are one byte.
+ *
+ * 0001
+ * 0000
+ * Low byte of block length (data byte count + 6 for header, excludes checksum)
+ * High byte of block length
+ * Low byte of load address
+ * High byte of load address
+ * Data byte 0
+ * ...
+ * Data byte N
+ * Checksum
*
- * I am unsure of the checksum format and need to check the SIMH source.
- * In the meantime a zero checksum still functions, albeit with a warning.
+ * The 8-bit checksum for a block is the twos-complement of the lower eight
+ * sum bits for all six header bytes and all data bytes.
+ *
+ * If the block length is exactly six bytes (i.e. only header, no data),
+ * then the block marks the end-of-tape. The checksum should be zero. If
+ * the load address of this final block is not 000001, then it is used as
+ * the starting PC.
*/
- uint16_t size = 6;
+ uint32_t checksum = 0;
+ uint32_t size = 6;
+ uint8_t data;
- uint16_t header[] = {1,0,01000};
- fwrite(header,6,1,dst);
+ /* Write header for data block. */
+ for (int i = 0; i < size; i++) {
+ switch (i) {
+ case 0: data = 0001; break;
+ case 1: data = 0000; break;
+ case 2: data = 0000; break; /* Size will be populated later */
+ case 3: data = 0000; break; /* Size will be populated later */
+ case 4: data = address & 0xff; break;
+ case 5: data = (address >> 8) & 0xff; break;
+ }
+ if (!fwrite(&data, 1, 1, dst)) {
+ fprintf(stderr, "ERROR: Failed to write block header.\n");
+ exit(EXIT_FAILURE);
+ }
+ checksum += data;
+ }
- uint8_t byte;
- int read;
- do {
- read = fread(&byte,1,1,src);
- if(read == 1) fwrite(&byte,1,1,dst);
- size += read;
- } while (read == 1);
+ /* Write contents of data block. */
+ while (1) {
+ if (fread(&data, 1, 1, src)) {
+ if (!fwrite(&data, 1, 1, dst)) {
+ fprintf(stderr, "ERROR: Failed to write block data.\n");
+ exit(EXIT_FAILURE);
+ }
+ size++;
+ checksum += data;
+ } else {
+ break;
+ }
+ }
+ fclose(src);
- uint16_t footer[] = {0,1,6,01000,0};
- fwrite(footer,10,1,dst);
+ /* Now that block size is known, update block header. */
+ if (fseek(dst, 2, SEEK_SET)) {
+ fprintf(stderr, "ERROR: Failed seek back to header of data block.\n");
+ exit(EXIT_FAILURE);
+ }
+ for (int i = 0; i < 2; i++) {
+ switch (i) {
+ case 0: data = size & 0xff; break;
+ case 1: data = (size >> 8) & 0xff; break;
+ }
+ if (!fwrite(&data, 1, 1, dst)) {
+ fprintf(stderr, "ERROR: Failed to write block size into header.\n");
+ exit(EXIT_FAILURE);
+ }
+ checksum += data; // Header is included in checksum.
+ }
+ if (fseek(dst, 0, SEEK_END)) {
+ fprintf(stderr, "ERROR: Failed seek to end of data block.\n");
+ exit(EXIT_FAILURE);
+ }
- fseek(dst,2,SEEK_SET);
- fwrite(&size,2,1,dst);
+ /* Write checksum for data block. */
+ checksum = (~checksum) + 1;
+ data = checksum & 0xff;
+ if (!fwrite(&data, 1, 1, dst)) {
+ fprintf(stderr, "ERROR: Failed to write checksum.\n");
+ exit(EXIT_FAILURE);
+ }
- fclose(src);
+ /* Write empty block to indicate end-of-tape. */
+ for (int i = 0; i < 8; i++) {
+ switch (i) {
+ case 0: data = 0001; break;
+ case 1: data = 0000; break;
+ case 2: data = 0006; break;
+ case 3: data = 0000; break;
+ case 4: data = address & 0xff; break;
+ case 5: data = (address >> 8) & 0xff; break;
+ case 6: data = 0000; break;
+ case 7: data = 0000; break;
+ }
+ if (!fwrite(&data, 1, 1, dst)) {
+ fprintf(stderr, "ERROR: Failed to write end-of-tape block.\n");
+ exit(EXIT_FAILURE);
+ }
+ }
fclose(dst);
}