I den föregående artikeln jämförde jag en CP/M baserad dator från tidsperioden 1975-85, dock emulerad, med dagens datorer. Min konklusion var att våra nuvarande datorer i alla relevanta avseenden är från tusen till några miljoner gånger kraftfullare än gårdagens CP/M maskin som trots dess begränsningar var ett fint hjälpmedel för många småföretag.
En CP/M maskinen erbjöd t.ex. en textbehandlare för brev vilket var himmelriket jämfört med att skriva ett helt brev felfritt på en mekanisk skrivmaskin. Mot slutet av CP/M epoken fanns det redan fullständigt användbara textbehandlare t.ex. Wordstar. CP/M maskinen erbjöd också program där mitt brev automatiskt kunde omformas för olika mottagare (mail merge) vilket igen sparade mycket tid igen i förhållande till manuellt arbete där varje brev, helst felfritt, skulle skrivas ut på skrivmaskin. Det fanns primitiva databaser och program för att hantera fakturering. Maskinen var primitiv men den var användbar.
Min jämförelse av CP/M maskinen och en modern PC gjordes på min bordsdator så att jag installerade emulatorn RunCPM under Linux. Då jag lekte med emulatorn märkte jag att det skulle ha varit trevligt att inifrån CP/M kunna skriva ut text på min skrivare samt naturligtvis också kunna lagra data på klassiskt sätt genom att stansa ut en hålremsa. En stansapparat är idag svår att komma över och de är förvånande dyra, vi talar ofta om kanske en tusenlapp. Om jag kan skapa hålremsan så behöver jag naturligtvis också ett program för att läsa av en hålremsa och återskapa data. Det kunde också vara trevligt att kunna lagra data och program på magnetband på ett möjligast tidstypiskt sätt. På samma sätt som för hålremsstansen så behöver jag naturligtvis också en läsare för magnetband.
Jag beslöt att lösa problemet med hjälp av några enkla program skrivna i programmeringsspråket Python. Dessa program körs under Linux parallellt med den emulerade CP/M maskinen. Notera att en modern PC har 4-8 processorkärnor, ibland mer, vilket betyder att den fullständigt problemfritt kan emulera min CP/M maskin utan att det märkbart påverkar Linuxens, d.v.s. PC:ns kapacitet.
Hålremsa
Det första steget för att ge min CP/M emulator tillgång till en hålremsstans och motsvarande läsare är att skriva två relativt enkla python program som tar en datafil d.v.s. en serie Bytes som i princip är godtyckliga tecken som passar in i en 8-bitars Byte. Totalt finns det max 256 tecken i en åtta bitars datastruktur.
Det slog mig att jag kunde använda detta projekt till att testa hur väl AI kunde hjälp mig med programmeringen och vilken form detta kunde ta. Min diskussion med AI är alltså den ”programmering” jag gjorde. Jag använde en gratisversion av chatGPT som medhjälpare. Texten nedan visar diskussionen med AI och resultatet. Efter varje programförslag korrigerade jag AI och bad om förbättringar. Jag använde engelska under diskussionen men jag gissar att det skulle ha fungerat lika bra på svenska.
Steg 1 jag beskriver grunddesignen för AI:
(Lars) I want to encode a string of 8 bit bytes into a png image as a column of bits. Every bit consists of 10×10 pixels. A high bit is white and a low bit is black. A maximum of 128 bytes are coded into a column. When a column is full a new column is generated to the right of the previous column. Columns are separated by a double bit wide grey separator line. The number of columns in the image is not restricted. The bytes to be encoded are read from a text file given on the command line. The resulting image file is called tape.png .
(ChatGPT) This is a neat “visual tape” encoding idea. The requirements are clear enough to implement directly. Below is a complete Python script using Pillow that does exactly what you described.
Resultatet var ett körbart pythonprogram som kodade data som strimmor vertikalt ner över skärmen. I princip användbart men motsvarar inte en gammal hålremsa. Det är lätt att be om en korrektion. Tanken är att generera en bild jag kan skriva ut på skrivare och om jag så vill läsa in med hjälp av skrivarens scanner för att sedan avkoda. Stansning/avkodning görs alltså modernt via en bild av typen png d.v.s. utan fysisk stansning. Resultatet kan dock skrivas ut och mappas in i en fysisk mapp om jag vill i likhet med forntidens hålremsa som förvarades i en ask.
(Lars) Generate columns of 128 bytes in a manner of a punched paper tape. When 128 bytes has filled a column new columns are generated to the right of the previous filled column.
(ChatGPT) What you’re describing is essentially a visual punched paper tape, where each column is like a vertical slice of tape holding 128 bytes (rows), and each byte is represented by 8 holes (bits).Let me tighten and clarify the structure and give you a clean implementation that matches that mental model exactly.
För att göra hålremsan lättare att läsa för en människa separerar jag varje kodad byte med en vit rad. Vi kunde klippa ut vertikala strimmor ur den utskrivna bilden och limma ihop dem till en hålremsa som vi kunde läsa av utan större problem på optisk väg. Någon hålstansning begövs inte för en modern läsare. En svart ruta med storleken 10×10 pixlar betyder att ifrågavarande bit=0 d.v.s. låg nivå. På motsvarande sätt betyder en vit ruta att platsen ”har stansats” d.v.s. bit=1 hög nivå. Svart/vit ruta förstås så att vi i en riktig pappersremsa har en lampa på baksidan av remsan som belyser sensorer som belyser sensorer på andra sidan. Ett stansat hål ses som ljust.
(Lars) Separate bytes with white line between rows of bits.
(ChatGPT) Got it — you want the tape to be more readable by clearly separating each byte (row) with a horizontal white line.
Slutresultatet blev ett kodningsprogram som jag kunde köra från kommandoraden under Linux:
python3 encoder3.py encoder3.py
Det första kommandot python3 anger att jag vill använda python3 som tolk, det finns flera generationer av python i bruk därav python3. Jag kan enkelt modifiera programmet så att det kan köras utan explicit python3. Jag ber programmet koda sig självt och resultatet blir en bildfil tape.png . Bildfilen har följande utseende:

Bilden innehåller alltså hela texten till programmet som kodar bilden. Hålremsan skall läsas uppifrån till vänster en kolumn i taget. Det finns inga inbyggda begränsningar för hur många kolumner det får finnas i bilden. Utskrivet på en A4 sida ryms ca. 3000 tecken d.v.s. ett typisk litet program skrivet t.ex. i Pascal eller C. Jag har inte editerat en enda rad i den genererade koden, allt är skapat av chatGPT enligt mina instruktioner.
Det genererade programmet har följande utseende utan en enda editering från min sida:
import sys
from PIL import Image
# Geometry
BIT_SIZE = 10
BITS_PER_BYTE = 8
BYTES_PER_COLUMN = 128
SEPARATOR_WIDTH_BITS = 2
ROW_SEPARATOR_BITS = 1 # NEW
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (128, 128, 128)
def byte_to_bits(byte):
return [(byte >> (7 - i)) & 1 for i in range(8)]
def main():
if len(sys.argv) < 2:
print("Usage: python tape.py input.bin")
return
with open(sys.argv[1], "rb") as f:
data = f.read()
# Split into columns
columns = [
data[i:i + BYTES_PER_COLUMN]
for i in range(0, len(data), BYTES_PER_COLUMN)
]
num_columns = len(columns)
# Height in bits (bytes + separators)
height_bits = (
BYTES_PER_COLUMN +
(BYTES_PER_COLUMN - 1) * ROW_SEPARATOR_BITS
)
# Width in bits
width_bits = (
num_columns * BITS_PER_BYTE +
(num_columns - 1) * SEPARATOR_WIDTH_BITS
)
# Convert to pixels
width_px = width_bits * BIT_SIZE
height_px = height_bits * BIT_SIZE
img = Image.new("RGB", (width_px, height_px), BLACK)
pixels = img.load()
current_x_bit = 0
for col_idx, column in enumerate(columns):
y_bit = 0
for row_idx, byte in enumerate(column):
bits = byte_to_bits(byte)
# Draw byte row
for bit_idx, bit in enumerate(bits):
color = WHITE if bit else BLACK
x_bit = current_x_bit + bit_idx
for dx in range(BIT_SIZE):
for dy in range(BIT_SIZE):
x = x_bit * BIT_SIZE + dx
y = y_bit * BIT_SIZE + dy
pixels[x, y] = color
y_bit += 1
# Draw horizontal separator (except after last byte)
if row_idx < len(column) - 1:
for x_bit in range(current_x_bit, current_x_bit + BITS_PER_BYTE):
for dx in range(BIT_SIZE):
for dy in range(BIT_SIZE):
x = x_bit * BIT_SIZE + dx
y = y_bit * BIT_SIZE + dy
pixels[x, y] = WHITE
y_bit += ROW_SEPARATOR_BITS
current_x_bit += BITS_PER_BYTE
# Draw vertical separator between columns
if col_idx < num_columns - 1:
for sep in range(SEPARATOR_WIDTH_BITS):
for yb in range(height_bits):
for dx in range(BIT_SIZE):
for dy in range(BIT_SIZE):
x = (current_x_bit + sep) * BIT_SIZE + dx
y = yb * BIT_SIZE + dy
pixels[x, y] = GRAY
current_x_bit += SEPARATOR_WIDTH_BITS
img.save("tape.png")
print("Saved tape.png")
if __name__ == "__main__":
main()
Följande steg är naturligtvis att låta chatGPT göra motsatsen d.v.s. skriva ett pythonprogram som läser en hålremsa lagrad som en bildfil och återskapar den kodade informationen.
(Lars) Now generate a decoder for the last version. It takes a png-file as input and produces a text file RDTAPE.txt containing the decoded bytes.
(ChatGPT) Great — now we’ll reverse the exact format you just defined.
This decoder will:
Read tape.png
Detect bits based on pixel blocks (10×10)
Skip:
horizontal white row separators
vertical gray column separators
Reconstruct bytes
Write them to RDTAPE.txt (binary-safe)
Resultatet blev ett fungerande avkodningsprogram som korrekt återskapade det kodade programmet i bildfilen tape.png.
import sys
from PIL import Image
# Geometry (must match encoder!)
BIT_SIZE = 10
BITS_PER_BYTE = 8
BYTES_PER_COLUMN = 128
ROW_SEPARATOR_BITS = 1
SEPARATOR_WIDTH_BITS = 2
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (128, 128, 128)
def is_white(pixel):
return pixel[0] > 200 and pixel[1] > 200 and pixel[2] > 200
def is_black(pixel):
return pixel[0] < 50 and pixel[1] < 50 and pixel[2] < 50
def is_gray(pixel):
return abs(pixel[0] - 128) < 40 and abs(pixel[1] - 128) < 40 and abs(pixel[2] - 128) < 40
def read_bit_block(pixels, x_bit, y_bit):
"""Sample center of a bit block"""
x = x_bit * BIT_SIZE + BIT_SIZE // 2
y = y_bit * BIT_SIZE + BIT_SIZE // 2
pixel = pixels[x, y]
if is_white(pixel):
return 1
else:
return 0
def is_separator_column(pixels, x_bit, height_bits):
"""Detect gray separator column"""
count_gray = 0
for yb in range(height_bits):
x = x_bit * BIT_SIZE + BIT_SIZE // 2
y = yb * BIT_SIZE + BIT_SIZE // 2
if is_gray(pixels[x, y]):
count_gray += 1
return count_gray > height_bits * 0.8
def main():
if len(sys.argv) < 2:
print("Usage: python decode.py tape.png")
return
img = Image.open(sys.argv[1])
pixels = img.load()
width_px, height_px = img.size
width_bits = width_px // BIT_SIZE
height_bits = height_px // BIT_SIZE
x_bit = 0
decoded_bytes = []
while x_bit < width_bits:
# Skip gray separator columns
if is_separator_column(pixels, x_bit, height_bits):
x_bit += 1
continue
# Read one data column (8 bits wide)
column_bytes = []
y_bit = 0
row_count = 0
while y_bit < height_bits and row_count < BYTES_PER_COLUMN:
bits = []
# Read 8 bits across
for bit_idx in range(BITS_PER_BYTE):
bits.append(read_bit_block(pixels, x_bit + bit_idx, y_bit))
# Convert to byte
value = 0
for b in bits:
value = (value << 1) | b
column_bytes.append(value)
row_count += 1
y_bit += 1
# Skip white separator row
if row_count < BYTES_PER_COLUMN:
y_bit += ROW_SEPARATOR_BITS
decoded_bytes.extend(column_bytes)
x_bit += BITS_PER_BYTE
# Write output
with open("RDTAPE.txt", "wb") as f:
f.write(bytes(decoded_bytes))
print("Decoded to RDTAPE.txt")
if __name__ == "__main__":
main()
Konklusion
Vi kan ser hur arbetsprocessen då jag använder AI för programmering förändras radikalt. Jag behöver inte längre i detalj kunna programmeringsaspråkets syntax och semantik. Jag försöker endast förklara för AI, så väl jag kan, exakt vad jag vill ha på en hög nivå i form av naturligt språk. Det är självklart att jag fortfarande måste känna problemkontexten. Jag måste ha en mental bild av hur data är kodat i Byte, Bit samt veta hur forntidens hålremsa i princip såg ut. Jag behöver däremot inte i detalj veta hur problemet skall splittras up i form av datastrukturer och funktioner för att skapa ett fungerande program, detta görs av AI.
Min uppfattning är att AI är ett verktyg som motsvarar övergången från handverktyg till långt automatiserade verkstäder. Det krävs mycket kunnande för att styra AI men kunskapen som behövs ligger på en betydligt högre abstraktionsnivå.
Jag kör den nya programkod AI ger vid varje iteration och försöker förklara för AI vad jag vill korrigera. Jag uppfattar arbetsprocessen som den jag körde med i forntiden då jag sålde egna industri mätsystem till kund. Det gällde då att själv vara i samma situation som chatGPT d.v.s. försöka förstå vad kunden egentligen försökte köpa. Då jag visste vad problemet var i detalj var inte implementationen något väldigt stort problem.
Fortsättning
Min tanke är att skapa automatik där jag använder Linux på huvuddatorn till att hantera utskrift till ”hålremsestansen”. På motsvarande sätt vill jag enkelt komma åt lagrade program och filer i mitt virtuella ”bibliotek” bestående av lagrade hålremsor. I forntiden skulle jag ha varit tvungen att gå till ett skåp och söka fram rätt hålremsa och hoppas att den var korrekt märkt, lägga in hålremsan i läsaren samt starta avläsningen. I mitt system bygger jag upp det så att jag kan be hålremsläsaren hämta rätt hålremsa och läsa av den. Mitt system motsvarar inte helt det som användes i forntiden men det är relativ nära. Jag behöver inte stiga upp ur stolen för att hämta rätt hålremsa. Jag har inte heller implementerat läsning via CP/M BIOS-anrop d.v.s. min arbetsprocess kommere att skilja sig från motsvarande process i forntiden.
Min plan är att förutom en stans för hålremsor också skapa ett enkelt sätt att inifrån CP/M skriva ut textfiler till Linux skrivare. Även här tänker jag fuska i viss mån dock så att systemet ungefär fungerar som i forntiden.
Om energi och intresse räcker till kan det hända att jag också skapar system för att lagra CP/M program och data till filer på magnetband. Jag använder i såfall linuxens ljudsystem för att skapa ljudfiler som kan läsas och återskapas till program eller data.
Mera om implementation och AI-hjälp i nästa artikel.
Lämna en kommentar