aboutsummaryrefslogtreecommitdiffstats
path: root/atk16_utils
diff options
context:
space:
mode:
authorJan Tuomi <jans.tuomi@gmail.com>2024-02-21 12:07:07 +0200
committerJan Tuomi <jans.tuomi@gmail.com>2024-02-21 12:07:07 +0200
commit927c018f12e73c254cec32c0f60e1c01b9fc0319 (patch)
tree21fbb975e3567cb0d9ab3fb34519c50325571d6c /atk16_utils
parenta0b4a1a6107e4dc905b21858d8ac76c47293cfb4 (diff)
Refactor names
Diffstat (limited to 'atk16_utils')
-rw-r--r--atk16_utils/charmem.py63
-rw-r--r--atk16_utils/convert_ttf.py52
-rw-r--r--atk16_utils/dig_install.py60
-rw-r--r--atk16_utils/pad_bin.py59
-rwxr-xr-xatk16_utils/ucode.py112
5 files changed, 346 insertions, 0 deletions
diff --git a/atk16_utils/charmem.py b/atk16_utils/charmem.py
new file mode 100644
index 0000000..aaac122
--- /dev/null
+++ b/atk16_utils/charmem.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+# Generate .bin file with ATK16 CPU TPU charmem data
+
+# Addressed by _CCC CCCC CYYY
+# Data width 8bit: ___X XXXX
+# where _ = unused, C = 8bit character code, Y = character y coordinate, X = pixel value
+
+import sys
+
+if len(sys.argv) != 2:
+ print("usage: charmem.py <outfile.bin>")
+ sys.exit(1)
+
+outfile_path = sys.argv[1]
+
+with open("charset.txt", "r") as f:
+ lines = [s.strip("\n") for s in f.readlines()]
+
+header_chars = lines[0]
+
+charset: list[list[str]] = []
+
+CHAR_HEIGHT = 8
+CHAR_WIDTH = 8
+
+i = 0
+while i < len(lines):
+ i += 1 # skip comment line
+ char = lines[i:i+CHAR_HEIGHT]
+ for row in char:
+ if len(row) != CHAR_WIDTH:
+ raise Exception(f"char row number {i} has width != {CHAR_WIDTH}: {len(row)}\n" + row + "\n" + "\n".join(char))
+ charset.append(char)
+ i += CHAR_HEIGHT + 1
+
+CHAR_N = len(charset)
+
+TOTAL_BYTEARRAY_SIZE = CHAR_N * CHAR_HEIGHT
+
+def string_to_byte(input_string: str):
+ # Replace spaces with '0' and '#' with '1'
+ binary_string = input_string.replace(' ', '0').replace('█', '1')
+
+ assert(len(input_string) == CHAR_WIDTH)
+
+ # Check if the string contains only '0' or '1'
+ if not all(c in '01' for c in binary_string):
+ raise ValueError("Input string must contain only spaces and '█' characters.")
+
+ # Convert the binary string to an unsigned integer byte
+ return int(binary_string[::-1], 2)
+
+
+res_b = bytearray(TOTAL_BYTEARRAY_SIZE)
+for cn, char in enumerate(charset):
+ for cy in range(0, CHAR_HEIGHT):
+ addr = (cn << 3) + cy
+ byte = string_to_byte(char[cy])
+ print(f"{byte:>08b}")
+ res_b[addr] = byte
+
+with open(outfile_path, "wb") as f:
+ f.write(res_b)
diff --git a/atk16_utils/convert_ttf.py b/atk16_utils/convert_ttf.py
new file mode 100644
index 0000000..c1135cb
--- /dev/null
+++ b/atk16_utils/convert_ttf.py
@@ -0,0 +1,52 @@
+from PIL import Image, ImageFont, ImageDraw
+
+import sys
+
+if len(sys.argv) != 3:
+ print("usage: convert_ttf.py <infile.ttf> <outfile.txt>")
+ sys.exit(1)
+
+# Define the font file and size
+font_file = sys.argv[1]
+out_file = sys.argv[2]
+font_size = 8 # 8x8 pixels
+
+# Create a font object
+font = ImageFont.truetype(font_file, font_size)
+
+
+# backspace 0x8, tab 0x9, newline 0xA, space 0x20
+
+# Define the characters to render
+#characters = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+
+def to_char_code(i: int) -> str:
+ if i == 10:
+ return " "
+ return chr(i)
+
+characters = [to_char_code(i) for i in range(0, 256)]
+
+
+# Open a file to write the bitmaps to
+with open(out_file, 'w') as bitmap_file:
+ for char_idx, char in enumerate(characters):
+ # Create a new image with a white background
+ image = Image.new('1', (8, 8), 1)
+
+ # Create a drawing context
+ draw = ImageDraw.Draw(image)
+
+ # Draw the character
+ draw.text((0, 0), char, font=font, fill=0)
+
+ # Convert the image to a list of 0's and 1's
+ pixels = image.getdata()
+ bitmap = [1 if pixel == 0 else 0 for pixel in pixels]
+
+ # Write the bitmap to the file
+ bitmap_file.write(f'Character {char_idx}: {char}\n')
+ for i in range(0, 64, 8):
+ row = bitmap[i:i+8]
+ bitmap_file.write(''.join(str(bit) for bit in row) + '\n')
+ bitmap_file.write('\n')
diff --git a/atk16_utils/dig_install.py b/atk16_utils/dig_install.py
new file mode 100644
index 0000000..c32229b
--- /dev/null
+++ b/atk16_utils/dig_install.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+# Install a generated .bin file into a memory chip in a .dig circuit file
+
+import sys
+from typing import cast
+import xml.etree.ElementTree as ET
+
+if len(sys.argv) != 4:
+ print("usage: dig_install.py <infile.bin> <circuitfile.dig> <chip_label>")
+ sys.exit(1)
+
+infile = sys.argv[1]
+circuitfile = sys.argv[2]
+chip_label = sys.argv[3]
+
+with open(infile, "rb") as f:
+ program_bytes = f.read()
+
+i = 0
+program_words: list[str] = []
+while i < len(program_bytes):
+ hi_byte = program_bytes[i]
+ lo_byte = program_bytes[i + 1]
+ word = f"{hi_byte:>02x}{lo_byte:>02x}"
+ #print(word)
+ program_words.append(word)
+ i += 2
+
+program_data = ",".join(program_words)
+print("program_data:", program_data)
+
+tree = ET.parse(circuitfile)
+root = tree.getroot()
+
+# Iterate through each 'visualElement' element
+def find_data_element(root: ET.Element) -> ET.Element:
+ is_correct_visual_element = False
+ for visual_element in root.findall('.//visualElement'):
+ element_attributes = visual_element.find('elementAttributes')
+ if element_attributes is None: continue
+
+ entries = list(element_attributes)
+ for entry in entries:
+ strings = entry.findall("string")
+ is_label_entry = len(strings) == 2 and strings[0].text == "Label" and strings[1].text == chip_label
+ if is_label_entry and not is_correct_visual_element:
+ is_correct_visual_element = True
+ continue
+
+ is_data_entry = len(strings) == 1 and strings[0].text == "Data"
+ if is_data_entry and is_correct_visual_element:
+ data_element = entry.findall("data")[0]
+ return data_element
+
+ raise Exception(f"No data entry matching label {chip_label} found in tree")
+
+data_element = find_data_element(root)
+data_element.text = program_data
+
+tree.write(circuitfile) \ No newline at end of file
diff --git a/atk16_utils/pad_bin.py b/atk16_utils/pad_bin.py
new file mode 100644
index 0000000..9f014d1
--- /dev/null
+++ b/atk16_utils/pad_bin.py
@@ -0,0 +1,59 @@
+import os
+
+def pad_binary(file_path, target_size) -> int:
+ """
+ Pads a binary file with zeros until it reaches a specified size in KiB.
+
+ :param file_path: Path to the binary file to be padded.
+ :param target_size: Target size in bytes.
+ """
+
+ # Check current file size
+ current_size = os.path.getsize(file_path)
+
+ # Calculate needed padding
+ padding_size = target_size - current_size
+
+ # Append zeros if needed
+ if padding_size > 0:
+ with open(file_path, 'ab') as file:
+ file.write(b'\x00' * padding_size)
+
+ return padding_size
+
+def convert_size_to_bytes(size_str: str) -> int:
+ """
+ Converts a size string with K, M, or G suffix to bytes.
+
+ :param size_str: Size string (e.g., "64K", "1M", "2G").
+ :return: Size in bytes.
+ """
+ size_str = size_str.upper()
+ if size_str.endswith('K'):
+ return int(size_str[:-1]) * 1024
+ elif size_str.endswith('M'):
+ return int(size_str[:-1]) * 1024 ** 2
+ elif size_str.endswith('G'):
+ return int(size_str[:-1]) * 1024 ** 3
+ else:
+ return int(size_str)
+
+if __name__ == "__main__":
+ import argparse
+
+ # Setup argument parser
+ parser = argparse.ArgumentParser(description='Pad a binary file with zeros until it reaches a specified size.')
+ parser.add_argument('file', type=str, help='Path to the binary file to be padded.')
+ parser.add_argument('--to', dest='size', type=str, help='Target size (e.g., 64K, 1M, 2G).', required=True)
+
+ args = parser.parse_args()
+
+ # Convert size argument to bytes
+ target_size_bytes = convert_size_to_bytes(args.size)
+
+ padded_bytes_n = pad_binary(args.file, target_size_bytes)
+
+ if padded_bytes_n > 0:
+ print(f"File has been padded with {padded_bytes_n} zeros.")
+ else:
+ print("File is already equal to or larger than the target size. No padding added.")
diff --git a/atk16_utils/ucode.py b/atk16_utils/ucode.py
new file mode 100755
index 0000000..22a2ce2
--- /dev/null
+++ b/atk16_utils/ucode.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+# Generate .bin file with ATK16 CPU microcode
+
+# Addressed by OOOO BUUU
+# where O = opcode, B = branch flag, U = microsequencer value
+
+import sys
+
+if len(sys.argv) != 2:
+ print("usage: ucode.py <outfile.bin>")
+ sys.exit(1)
+
+outfile_path = sys.argv[1]
+
+PC_CO = 1 << 0
+PC_IE = 1 << 1
+PC_OE = 1 << 2
+MAR_IE = 1 << 3
+MEM_IE = 1 << 4
+MEM_OE = 1 << 5
+RW_IE = 1 << 6
+R1_OE = 1 << 7
+R2_OE = 1 << 8
+IR_IE = 1 << 9
+IM_M = 1 << 10
+LI_OE = 1 << 11
+ALU_OE = 1 << 12
+FR_IE = 1 << 13
+HALT = 1 << 14
+US_RS = 1 << 15
+ISRA_OE = 1 << 16
+IM_DS = 1 << 17
+IM_EN = 1 << 18
+IPC_IE = 1 << 19
+IPC_OE = 1 << 20
+NOP5 = 1 << 21
+NOP6 = 1 << 22
+NOP7 = 1 << 23
+
+BRANCH_FLAG_STATES_N = 2
+UCODE_N: int = 2**3
+CONTROL_WORD_SIZE = 3
+
+def not_branch(bs: list[int]) -> list[list[int]]:
+ return BRANCH_FLAG_STATES_N * [bs]
+
+def branch(false_branch: list[int], true_branch: list[int]) -> list[list[int]]:
+ return [false_branch, true_branch]
+
+fetch = [PC_OE|MAR_IE, MEM_OE|IR_IE|PC_CO]
+
+def nop():
+ return not_branch([*fetch, US_RS, 0, 0, 0, 0, 0])
+
+ucode = [
+ # ALR 0000 TTTL LLRR RSSS
+ not_branch([*fetch, ALU_OE|FR_IE|RW_IE, US_RS, 0, 0, 0, 0]),
+ # ALI 0001 TTTL LLII ISSS
+ not_branch([*fetch, IM_M|ALU_OE|FR_IE|RW_IE, US_RS, 0, 0, 0, 0]),
+ # LDR 0010 TTTR RRXX XXXX
+ not_branch([*fetch, R1_OE|MAR_IE, MEM_OE|RW_IE, US_RS, 0, 0, 0]),
+ # STR 0011 XXXL LLRR RXXX
+ not_branch([*fetch, R1_OE|MAR_IE, R2_OE|MEM_IE, US_RS, 0, 0, 0]),
+ # LDI 0100 TTTI IIII IIII
+ not_branch([*fetch, LI_OE|RW_IE, US_RS, 0, 0, 0, 0]),
+ # JPR 0101 XXXR RRXX XXXX
+ not_branch([*fetch, R1_OE|PC_IE, US_RS, 0, 0, 0, 0]),
+ # JPI 0110 XXXI IIII IIII
+ not_branch([*fetch, IM_M|LI_OE|PC_IE, US_RS, 0, 0, 0, 0]),
+ # BRR 0111 XFFR RRXX XXXX
+ branch([*fetch, US_RS, 0, 0, 0, 0, 0],
+ [*fetch, R1_OE|PC_IE, US_RS, 0, 0, 0, 0]),
+ # BRI 1000 XFFI IIII IIII
+ branch([*fetch, US_RS, 0, 0, 0, 0, 0],
+ [*fetch, IM_M|LI_OE|PC_IE, US_RS, 0, 0, 0, 0]),
+ # LPC 1001 TTTX XXXX XXXX
+ not_branch([*fetch, PC_OE|RW_IE, US_RS, 0, 0, 0, 0]),
+ # NOP 1010 XXXX XXXX XXXX
+ nop(),
+ # NOP 1011 XXXX XXXX XXXX
+ nop(),
+ # ISRP0 1100 XXXX XXXX XXXX
+ not_branch([IM_EN|PC_OE|IPC_IE, ISRA_OE|MAR_IE, MEM_OE|PC_IE, US_RS, 0, 0, 0, 0]),
+ # ISRP1 1101 XXXX XXXX XXXX
+ not_branch([IM_EN|PC_OE|IPC_IE, ISRA_OE|MAR_IE, MEM_OE|PC_IE, US_RS, 0, 0, 0, 0]),
+ # RTI 1110 XXXX XXXX XXXX
+ not_branch([*fetch, IM_DS|IPC_OE|PC_IE, US_RS, 0, 0, 0, 0]),
+ # HLT 1111 XXXX XXXX XXXX
+ not_branch([*fetch, HALT, 0, 0, 0, 0, 0]),
+]
+
+INST_N = len(ucode)
+TOTAL_BYTEARRAY_SIZE = INST_N * BRANCH_FLAG_STATES_N * UCODE_N * CONTROL_WORD_SIZE
+
+res_b = bytearray(TOTAL_BYTEARRAY_SIZE)
+
+for i in range(INST_N):
+ for j in range(BRANCH_FLAG_STATES_N):
+ for k in range(UCODE_N):
+ for l in range(CONTROL_WORD_SIZE):
+ idx = l + \
+ k * CONTROL_WORD_SIZE + \
+ j * CONTROL_WORD_SIZE * UCODE_N + \
+ i * CONTROL_WORD_SIZE * UCODE_N * BRANCH_FLAG_STATES_N
+ cword = ucode[i][j][k]
+ assert len(ucode[i][j]) == 8
+ cbyte = (cword >> (8 * (CONTROL_WORD_SIZE - l - 1))) & 0xff
+ print(f"inst: {i:>04b}, idx: {idx:>04x}, cbyte: {cbyte:>08b}")
+ res_b[idx] = cbyte
+
+with open(outfile_path, "wb") as f:
+ f.write(res_b)