
Can a LLM convert C, to ASM to specs and then to a working Z/80 Speccy tape? Yes.
Damien Guard nerd sniped me and and other folks wanted more proof that it is now cheap, easy and possible to cheaply rewrite software or clone existing "source available" businesses (see bottom of the post).
So, let's get cracking by creating a toy application by sending this prompt to the LLM.
Create a sales tax calculator in C
Food is taxed at 10%
Personal Hygine is at 0%
Everything else 25%
Which creates
#include <stdio.h>
#include <string.h>
#include <ctype.h>
// Tax rates
#define FOOD_TAX 0.10
#define HYGIENE_TAX 0.00
#define DEFAULT_TAX 0.25
// Item categories
#define FOOD 1
#define HYGIENE 2
#define OTHER 3
int getCategory() {
char choice;
printf("Enter item category (F for Food, H for Hygiene, O for Other): ");
scanf(" %c", &choice);
choice = toupper(choice);
switch(choice) {
case 'F': return FOOD;
case 'H': return HYGIENE;
case 'O': return OTHER;
default:
printf("Invalid category. Defaulting to Other.\n");
return OTHER;
}
}
double calculateTax(double price, int category) {
switch(category) {
case FOOD: return price * FOOD_TAX;
case HYGIENE: return price * HYGIENE_TAX;
case OTHER: return price * DEFAULT_TAX;
default: return price * DEFAULT_TAX;
}
}
int main() {
double price;
int category;
char another = 'Y';
while (toupper(another) == 'Y') {
printf("Enter item price: $");
scanf("%lf", &price);
category = getCategory();
double tax = calculateTax(price, category);
double total = price + tax;
printf("Price: $%.2f\n", price);
printf("Tax: $%.2f\n", tax);
printf("Total: $%.2f\n\n", total);
printf("Calculate another item? (Y/N): ");
scanf(" %c", &another);
}
return 0;
}
Now let's compile it via gcc calc.c -o calc
then objdump -d calc > calc.asm
to convert it to assembly which results in the following
calc: file format elf64-x86-64
Disassembly of section .init:
0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__@Base>
100f: 48 85 c0 test %rax,%rax
1012: 74 02 je 1016 <_init+0x16>
1014: ff d0 call *%rax
1016: 48 83 c4 08 add $0x8,%rsp
101a: c3 ret
Disassembly of section .plt:
0000000000001020 <.plt>:
1020: ff 35 7a 2f 00 00 push 0x2f7a(%rip) # 3fa0 <_GLOBAL_OFFSET_TABLE_+0x8>
1026: f2 ff 25 7b 2f 00 00 bnd jmp *0x2f7b(%rip) # 3fa8 <_GLOBAL_OFFSET_TABLE_+0x10>
102d: 0f 1f 00 nopl (%rax)
1030: f3 0f 1e fa endbr64
1034: 68 00 00 00 00 push $0x0
1039: f2 e9 e1 ff ff ff bnd jmp 1020 <_init+0x20>
103f: 90 nop
1040: f3 0f 1e fa endbr64
1044: 68 01 00 00 00 push $0x1
1049: f2 e9 d1 ff ff ff bnd jmp 1020 <_init+0x20>
104f: 90 nop
1050: f3 0f 1e fa endbr64
1054: 68 02 00 00 00 push $0x2
1059: f2 e9 c1 ff ff ff bnd jmp 1020 <_init+0x20>
105f: 90 nop
1060: f3 0f 1e fa endbr64
1064: 68 03 00 00 00 push $0x3
1069: f2 e9 b1 ff ff ff bnd jmp 1020 <_init+0x20>
106f: 90 nop
1070: f3 0f 1e fa endbr64
1074: 68 04 00 00 00 push $0x4
1079: f2 e9 a1 ff ff ff bnd jmp 1020 <_init+0x20>
107f: 90 nop
Disassembly of section .plt.got:
0000000000001080 <__cxa_finalize@plt>:
1080: f3 0f 1e fa endbr64
1084: f2 ff 25 6d 2f 00 00 bnd jmp *0x2f6d(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
108b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Disassembly of section .plt.sec:
0000000000001090 <toupper@plt>:
1090: f3 0f 1e fa endbr64
1094: f2 ff 25 15 2f 00 00 bnd jmp *0x2f15(%rip) # 3fb0 <toupper@GLIBC_2.2.5>
109b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010a0 <puts@plt>:
10a0: f3 0f 1e fa endbr64
10a4: f2 ff 25 0d 2f 00 00 bnd jmp *0x2f0d(%rip) # 3fb8 <puts@GLIBC_2.2.5>
10ab: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010b0 <__stack_chk_fail@plt>:
10b0: f3 0f 1e fa endbr64
10b4: f2 ff 25 05 2f 00 00 bnd jmp *0x2f05(%rip) # 3fc0 <__stack_chk_fail@GLIBC_2.4>
10bb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010c0 <printf@plt>:
10c0: f3 0f 1e fa endbr64
10c4: f2 ff 25 fd 2e 00 00 bnd jmp *0x2efd(%rip) # 3fc8 <printf@GLIBC_2.2.5>
10cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010d0 <__isoc99_scanf@plt>:
10d0: f3 0f 1e fa endbr64
10d4: f2 ff 25 f5 2e 00 00 bnd jmp *0x2ef5(%rip) # 3fd0 <__isoc99_scanf@GLIBC_2.7>
10db: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Disassembly of section .text:
00000000000010e0 <_start>:
10e0: f3 0f 1e fa endbr64
10e4: 31 ed xor %ebp,%ebp
10e6: 49 89 d1 mov %rdx,%r9
10e9: 5e pop %rsi
10ea: 48 89 e2 mov %rsp,%rdx
10ed: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
10f1: 50 push %rax
10f2: 54 push %rsp
10f3: 45 31 c0 xor %r8d,%r8d
10f6: 31 c9 xor %ecx,%ecx
10f8: 48 8d 3d fd 01 00 00 lea 0x1fd(%rip),%rdi # 12fc <main>
10ff: ff 15 d3 2e 00 00 call *0x2ed3(%rip) # 3fd8 <__libc_start_main@GLIBC_2.34>
1105: f4 hlt
1106: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
110d: 00 00 00
0000000000001110 <deregister_tm_clones>:
1110: 48 8d 3d f9 2e 00 00 lea 0x2ef9(%rip),%rdi # 4010 <__TMC_END__>
1117: 48 8d 05 f2 2e 00 00 lea 0x2ef2(%rip),%rax # 4010 <__TMC_END__>
111e: 48 39 f8 cmp %rdi,%rax
1121: 74 15 je 1138 <deregister_tm_clones+0x28>
1123: 48 8b 05 b6 2e 00 00 mov 0x2eb6(%rip),%rax # 3fe0 <_ITM_deregisterTMCloneTable@Base>
112a: 48 85 c0 test %rax,%rax
112d: 74 09 je 1138 <deregister_tm_clones+0x28>
112f: ff e0 jmp *%rax
1131: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
1138: c3 ret
1139: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001140 <register_tm_clones>:
1140: 48 8d 3d c9 2e 00 00 lea 0x2ec9(%rip),%rdi # 4010 <__TMC_END__>
1147: 48 8d 35 c2 2e 00 00 lea 0x2ec2(%rip),%rsi # 4010 <__TMC_END__>
114e: 48 29 fe sub %rdi,%rsi
1151: 48 89 f0 mov %rsi,%rax
1154: 48 c1 ee 3f shr $0x3f,%rsi
1158: 48 c1 f8 03 sar $0x3,%rax
115c: 48 01 c6 add %rax,%rsi
115f: 48 d1 fe sar %rsi
1162: 74 14 je 1178 <register_tm_clones+0x38>
1164: 48 8b 05 85 2e 00 00 mov 0x2e85(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable@Base>
116b: 48 85 c0 test %rax,%rax
116e: 74 08 je 1178 <register_tm_clones+0x38>
1170: ff e0 jmp *%rax
1172: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
1178: c3 ret
1179: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001180 <__do_global_dtors_aux>:
1180: f3 0f 1e fa endbr64
1184: 80 3d 85 2e 00 00 00 cmpb $0x0,0x2e85(%rip) # 4010 <__TMC_END__>
118b: 75 2b jne 11b8 <__do_global_dtors_aux+0x38>
118d: 55 push %rbp
118e: 48 83 3d 62 2e 00 00 cmpq $0x0,0x2e62(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
1195: 00
1196: 48 89 e5 mov %rsp,%rbp
1199: 74 0c je 11a7 <__do_global_dtors_aux+0x27>
119b: 48 8b 3d 66 2e 00 00 mov 0x2e66(%rip),%rdi # 4008 <__dso_handle>
11a2: e8 d9 fe ff ff call 1080 <__cxa_finalize@plt>
11a7: e8 64 ff ff ff call 1110 <deregister_tm_clones>
11ac: c6 05 5d 2e 00 00 01 movb $0x1,0x2e5d(%rip) # 4010 <__TMC_END__>
11b3: 5d pop %rbp
11b4: c3 ret
11b5: 0f 1f 00 nopl (%rax)
11b8: c3 ret
11b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000011c0 <frame_dummy>:
11c0: f3 0f 1e fa endbr64
11c4: e9 77 ff ff ff jmp 1140 <register_tm_clones>
00000000000011c9 <getCategory>:
11c9: f3 0f 1e fa endbr64
11cd: 55 push %rbp
11ce: 48 89 e5 mov %rsp,%rbp
11d1: 48 83 ec 10 sub $0x10,%rsp
11d5: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
11dc: 00 00
11de: 48 89 45 f8 mov %rax,-0x8(%rbp)
11e2: 31 c0 xor %eax,%eax
11e4: 48 8d 05 1d 0e 00 00 lea 0xe1d(%rip),%rax # 2008 <_IO_stdin_used+0x8>
11eb: 48 89 c7 mov %rax,%rdi
11ee: b8 00 00 00 00 mov $0x0,%eax
11f3: e8 c8 fe ff ff call 10c0 <printf@plt>
11f8: 48 8d 45 f7 lea -0x9(%rbp),%rax
11fc: 48 89 c6 mov %rax,%rsi
11ff: 48 8d 05 41 0e 00 00 lea 0xe41(%rip),%rax # 2047 <_IO_stdin_used+0x47>
1206: 48 89 c7 mov %rax,%rdi
1209: b8 00 00 00 00 mov $0x0,%eax
120e: e8 bd fe ff ff call 10d0 <__isoc99_scanf@plt>
1213: 0f b6 45 f7 movzbl -0x9(%rbp),%eax
1217: 0f be c0 movsbl %al,%eax
121a: 89 c7 mov %eax,%edi
121c: e8 6f fe ff ff call 1090 <toupper@plt>
1221: 88 45 f7 mov %al,-0x9(%rbp)
1224: 0f b6 45 f7 movzbl -0x9(%rbp),%eax
1228: 0f be c0 movsbl %al,%eax
122b: 83 f8 4f cmp $0x4f,%eax
122e: 74 1f je 124f <getCategory+0x86>
1230: 83 f8 4f cmp $0x4f,%eax
1233: 7f 21 jg 1256 <getCategory+0x8d>
1235: 83 f8 46 cmp $0x46,%eax
1238: 74 07 je 1241 <getCategory+0x78>
123a: 83 f8 48 cmp $0x48,%eax
123d: 74 09 je 1248 <getCategory+0x7f>
123f: eb 15 jmp 1256 <getCategory+0x8d>
1241: b8 01 00 00 00 mov $0x1,%eax
1246: eb 22 jmp 126a <getCategory+0xa1>
1248: b8 02 00 00 00 mov $0x2,%eax
124d: eb 1b jmp 126a <getCategory+0xa1>
124f: b8 03 00 00 00 mov $0x3,%eax
1254: eb 14 jmp 126a <getCategory+0xa1>
1256: 48 8d 05 f3 0d 00 00 lea 0xdf3(%rip),%rax # 2050 <_IO_stdin_used+0x50>
125d: 48 89 c7 mov %rax,%rdi
1260: e8 3b fe ff ff call 10a0 <puts@plt>
1265: b8 03 00 00 00 mov $0x3,%eax
126a: 48 8b 55 f8 mov -0x8(%rbp),%rdx
126e: 64 48 2b 14 25 28 00 sub %fs:0x28,%rdx
1275: 00 00
1277: 74 05 je 127e <getCategory+0xb5>
1279: e8 32 fe ff ff call 10b0 <__stack_chk_fail@plt>
127e: c9 leave
127f: c3 ret
0000000000001280 <calculateTax>:
1280: f3 0f 1e fa endbr64
1284: 55 push %rbp
1285: 48 89 e5 mov %rsp,%rbp
1288: f2 0f 11 45 f8 movsd %xmm0,-0x8(%rbp)
128d: 89 7d f4 mov %edi,-0xc(%rbp)
1290: 83 7d f4 03 cmpl $0x3,-0xc(%rbp)
1294: 74 36 je 12cc <calculateTax+0x4c>
1296: 83 7d f4 03 cmpl $0x3,-0xc(%rbp)
129a: 7f 43 jg 12df <calculateTax+0x5f>
129c: 83 7d f4 01 cmpl $0x1,-0xc(%rbp)
12a0: 74 08 je 12aa <calculateTax+0x2a>
12a2: 83 7d f4 02 cmpl $0x2,-0xc(%rbp)
12a6: 74 15 je 12bd <calculateTax+0x3d>
12a8: eb 35 jmp 12df <calculateTax+0x5f>
12aa: f2 0f 10 4d f8 movsd -0x8(%rbp),%xmm1
12af: f2 0f 10 05 21 0e 00 movsd 0xe21(%rip),%xmm0 # 20d8 <_IO_stdin_used+0xd8>
12b6: 00
12b7: f2 0f 59 c1 mulsd %xmm1,%xmm0
12bb: eb 33 jmp 12f0 <calculateTax+0x70>
12bd: f2 0f 10 4d f8 movsd -0x8(%rbp),%xmm1
12c2: 66 0f ef c0 pxor %xmm0,%xmm0
12c6: f2 0f 59 c1 mulsd %xmm1,%xmm0
12ca: eb 24 jmp 12f0 <calculateTax+0x70>
12cc: f2 0f 10 4d f8 movsd -0x8(%rbp),%xmm1
12d1: f2 0f 10 05 07 0e 00 movsd 0xe07(%rip),%xmm0 # 20e0 <_IO_stdin_used+0xe0>
12d8: 00
12d9: f2 0f 59 c1 mulsd %xmm1,%xmm0
12dd: eb 11 jmp 12f0 <calculateTax+0x70>
12df: f2 0f 10 4d f8 movsd -0x8(%rbp),%xmm1
12e4: f2 0f 10 05 f4 0d 00 movsd 0xdf4(%rip),%xmm0 # 20e0 <_IO_stdin_used+0xe0>
12eb: 00
12ec: f2 0f 59 c1 mulsd %xmm1,%xmm0
12f0: 66 48 0f 7e c0 movq %xmm0,%rax
12f5: 66 48 0f 6e c0 movq %rax,%xmm0
12fa: 5d pop %rbp
12fb: c3 ret
00000000000012fc <main>:
12fc: f3 0f 1e fa endbr64
1300: 55 push %rbp
1301: 48 89 e5 mov %rsp,%rbp
1304: 48 83 ec 30 sub $0x30,%rsp
1308: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
130f: 00 00
1311: 48 89 45 f8 mov %rax,-0x8(%rbp)
1315: 31 c0 xor %eax,%eax
1317: c6 45 db 59 movb $0x59,-0x25(%rbp)
131b: e9 f1 00 00 00 jmp 1411 <main+0x115>
1320: 48 8d 05 50 0d 00 00 lea 0xd50(%rip),%rax # 2077 <_IO_stdin_used+0x77>
1327: 48 89 c7 mov %rax,%rdi
132a: b8 00 00 00 00 mov $0x0,%eax
132f: e8 8c fd ff ff call 10c0 <printf@plt>
1334: 48 8d 45 e0 lea -0x20(%rbp),%rax
1338: 48 89 c6 mov %rax,%rsi
133b: 48 8d 05 49 0d 00 00 lea 0xd49(%rip),%rax # 208b <_IO_stdin_used+0x8b>
1342: 48 89 c7 mov %rax,%rdi
1345: b8 00 00 00 00 mov $0x0,%eax
134a: e8 81 fd ff ff call 10d0 <__isoc99_scanf@plt>
134f: b8 00 00 00 00 mov $0x0,%eax
1354: e8 70 fe ff ff call 11c9 <getCategory>
1359: 89 45 dc mov %eax,-0x24(%rbp)
135c: 48 8b 45 e0 mov -0x20(%rbp),%rax
1360: 8b 55 dc mov -0x24(%rbp),%edx
1363: 89 d7 mov %edx,%edi
1365: 66 48 0f 6e c0 movq %rax,%xmm0
136a: e8 11 ff ff ff call 1280 <calculateTax>
136f: 66 48 0f 7e c0 movq %xmm0,%rax
1374: 48 89 45 e8 mov %rax,-0x18(%rbp)
1378: f2 0f 10 45 e0 movsd -0x20(%rbp),%xmm0
137d: f2 0f 10 4d e8 movsd -0x18(%rbp),%xmm1
1382: f2 0f 58 c1 addsd %xmm1,%xmm0
1386: f2 0f 11 45 f0 movsd %xmm0,-0x10(%rbp)
138b: 48 8b 45 e0 mov -0x20(%rbp),%rax
138f: 66 48 0f 6e c0 movq %rax,%xmm0
1394: 48 8d 05 f4 0c 00 00 lea 0xcf4(%rip),%rax # 208f <_IO_stdin_used+0x8f>
139b: 48 89 c7 mov %rax,%rdi
139e: b8 01 00 00 00 mov $0x1,%eax
13a3: e8 18 fd ff ff call 10c0 <printf@plt>
13a8: 48 8b 45 e8 mov -0x18(%rbp),%rax
13ac: 66 48 0f 6e c0 movq %rax,%xmm0
13b1: 48 8d 05 e5 0c 00 00 lea 0xce5(%rip),%rax # 209d <_IO_stdin_used+0x9d>
13b8: 48 89 c7 mov %rax,%rdi
13bb: b8 01 00 00 00 mov $0x1,%eax
13c0: e8 fb fc ff ff call 10c0 <printf@plt>
13c5: 48 8b 45 f0 mov -0x10(%rbp),%rax
13c9: 66 48 0f 6e c0 movq %rax,%xmm0
13ce: 48 8d 05 d4 0c 00 00 lea 0xcd4(%rip),%rax # 20a9 <_IO_stdin_used+0xa9>
13d5: 48 89 c7 mov %rax,%rdi
13d8: b8 01 00 00 00 mov $0x1,%eax
13dd: e8 de fc ff ff call 10c0 <printf@plt>
13e2: 48 8d 05 cf 0c 00 00 lea 0xccf(%rip),%rax # 20b8 <_IO_stdin_used+0xb8>
13e9: 48 89 c7 mov %rax,%rdi
13ec: b8 00 00 00 00 mov $0x0,%eax
13f1: e8 ca fc ff ff call 10c0 <printf@plt>
13f6: 48 8d 45 db lea -0x25(%rbp),%rax
13fa: 48 89 c6 mov %rax,%rsi
13fd: 48 8d 05 43 0c 00 00 lea 0xc43(%rip),%rax # 2047 <_IO_stdin_used+0x47>
1404: 48 89 c7 mov %rax,%rdi
1407: b8 00 00 00 00 mov $0x0,%eax
140c: e8 bf fc ff ff call 10d0 <__isoc99_scanf@plt>
1411: 0f b6 45 db movzbl -0x25(%rbp),%eax
1415: 0f be c0 movsbl %al,%eax
1418: 89 c7 mov %eax,%edi
141a: e8 71 fc ff ff call 1090 <toupper@plt>
141f: 83 f8 59 cmp $0x59,%eax
1422: 0f 84 f8 fe ff ff je 1320 <main+0x24>
1428: b8 00 00 00 00 mov $0x0,%eax
142d: 48 8b 55 f8 mov -0x8(%rbp),%rdx
1431: 64 48 2b 14 25 28 00 sub %fs:0x28,%rdx
1438: 00 00
143a: 74 05 je 1441 <main+0x145>
143c: e8 6f fc ff ff call 10b0 <__stack_chk_fail@plt>
1441: c9 leave
1442: c3 ret
Disassembly of section .fini:
0000000000001444 <_fini>:
1444: f3 0f 1e fa endbr64
1448: 48 83 ec 08 sub $0x8,%rsp
144c: 48 83 c4 08 add $0x8,%rsp
1450: c3 ret
Now that we have the assembly, can we make it human readable and useful? Let's try the following prompt
Look at @calc.asm and generate feature specification as seperate files that describe the application. One per topic domain.

Which then produces a specification library - here's the functional overview...
# Tax Calculator - Functional Overview
## Description
The Tax Calculator is a command-line application that calculates taxes and total costs based on user input. The application allows users to enter an amount and select a tax category, then calculates the appropriate tax and displays the total.
## Key Features
1. **Amount Input**: Accepts a numerical amount as input from the user
2. **Category Selection**: Allows the user to select from different tax categories:
- F: Food category (5% tax)
- H: Healthcare category (0% tax - tax exempt)
- O: Other/Standard category (10% tax)
3. **Tax Calculation**: Calculates the tax amount based on the selected category and input amount
4. **Total Calculation**: Adds the tax to the original amount to calculate the total cost
5. **Results Display**: Shows the original amount, tax amount, and total amount to the user
6. **Multiple Calculations**: Allows the user to perform multiple calculations in a single session by asking if they want to continue
## Workflow
1. User is prompted to enter an amount
2. User selects a tax category
3. System calculates tax based on the category and amount
4. System displays the original amount, tax amount, and total amount
5. User is asked if they want to perform another calculation
6. If yes, the process repeats; if no, the application terminates
Here's the dataflow specification...
# Tax Calculator - Data Flow and Storage Specification
## Data Types and Storage
### User Input Data
- **Purchase Amount**: Stored as a double-precision floating-point number (64-bit)
- **Category Selection**: Initially captured as a character, then converted to an integer category ID
- **Continuation Flag**: Single character stored as a byte
### Calculated Data
- **Tax Amount**: Calculated and stored as a double-precision floating-point number (64-bit)
- **Total Amount**: Calculated and stored as a double-precision floating-point number (64-bit)
### Storage Duration
- All data is stored in stack-allocated variables
- Data persists only for the duration of a single calculation cycle
- No data persists between separate executions of the program
## Data Flow
### Input Flow
1. User enters purchase amount via keyboard
2. Input is captured through scanf and stored in a local variable
3. User enters category selection via keyboard
4. Character input is converted to uppercase and mapped to a category ID
5. Category ID is returned to the main function
### Processing Flow
1. Main function receives the purchase amount and category ID
2. These values are passed to the calculateTax function
3. calculateTax applies the appropriate tax rate based on the category ID
4. The calculated tax amount is returned to the main function
5. Main function adds the tax amount to the original amount to calculate the total
### Output Flow
1. Original amount is displayed to the user
2. Calculated tax amount is displayed to the user
3. Calculated total amount is displayed to the user
### Continuation Flow
1. User is prompted to continue or exit
2. Input is captured and converted to uppercase
3. If input is 'Y', the application loops back to the beginning of the calculation cycle
4. If input is anything else, the application terminates
## Data Transformation
### Character to Category Mapping
- 'F' (ASCII 0x46) -> Category ID 1 (Food)
- 'H' (ASCII 0x48) -> Category ID 2 (Healthcare)
- 'O' (ASCII 0x4F) -> Category ID 3 (Other)
- Any other character -> Category ID 3 (Other, default)
### Category to Tax Rate Mapping
- Category ID 1 (Food) -> 0.05 (5%)
- Category ID 2 (Healthcare) -> 0.00 (0%)
- Category ID 3 (Other) -> 0.10 (10%)
## Data Security
- No persistent storage of data
- No sensitive data is processed
- All variables are properly initialized before use
- Stack memory is protected against buffer overflows
Here's the business logic specification:
# Tax Calculator - Tax Calculation Specification
## Tax Categories
The application supports three different tax categories:
1. **Food (F)**:
- Category ID: 1
- Tax Rate: 5%
- Applied to all food items
2. **Healthcare (H)**:
- Category ID: 2
- Tax Rate: 0% (tax exempt)
- Applied to all healthcare-related items
3. **Other (O)**:
- Category ID: 3
- Tax Rate: 10%
- Default category for all other items
- Used as fallback when an invalid category is selected
## Calculation Formulas
### Tax Amount Calculation
```
taxAmount = amount * taxRate
```
Where:
- `amount` is the purchase amount entered by the user
- `taxRate` is the decimal tax rate based on the selected category (0.05 for Food, 0.0 for Healthcare, 0.10 for Other)
### Total Amount Calculation
```
totalAmount = amount + taxAmount
```
Where:
- `amount` is the original purchase amount
- `taxAmount` is the calculated tax amount
## Implementation Details
The tax calculation is implemented in the `calculateTax` function, which:
1. Takes two parameters: the purchase amount and the category ID
2. Applies the appropriate tax rate based on the category ID:
- Category 1 (Food): 5% tax rate
- Category 2 (Healthcare): 0% tax rate
- Category 3 (Other): 10% tax rate
3. Returns the calculated tax amount
The category selection is handled by the `getCategory` function, which:
1. Prompts the user to select a category
2. Converts the user input to uppercase
3. Maps the input character to a category ID:
- 'F' -> 1 (Food)
- 'H' -> 2 (Healthcare)
- 'O' -> 3 (Other)
4. Uses category 3 (Other) as default for invalid inputs
Plenty of other files were also generated but for the sake of brevity I'm not going to include them.

Now that we got the specification, let's strip all 'isms related to the C language out of the functional spec.
Look at @specs-from-calc-c. Remove all ANSI C aspects of the spec and write them to SPECS folder


Now this is where it gets interesting. We have successfully taken an application that was C, disassembled it into ASM, created a high level clean-room functional specification that describes the application. Cool. Now let's provide the LLM with the following prompt
Implement this as a Z/80 spectrum calculator program
Compile it
Add it to the disk image
Run the emulator with the disk image
You may install software
Which generates the sales tax calculator for the Z80 speccy.
; Tax Calculator for ZX Spectrum
; Based on provided specifications
; Implements a simple tax calculation program with three categories:
; F: Food (5% tax)
; H: Healthcare (0% tax)
; O: Other/Standard (10% tax)
; *********************************************************************
; Constants and System Variables
; *********************************************************************
; System constants
SCREEN_START equ 16384 ; Start of screen memory
ATTR_START equ 22528 ; Start of attribute memory
LAST_K equ 23560 ; Last key pressed system variable
CHANNEL_S equ 5633 ; ROM print routine (output to screen)
CLS equ 3503 ; ROM routine to clear screen
OPEN_CHANNEL equ 5633 ; ROM routine to open a channel
; Program constants
FOOD_RATE equ 5 ; 5% tax rate
HEALTHCARE_RATE equ 0 ; 0% tax rate
OTHER_RATE equ 10 ; 10% tax rate
; Decimal multiplier for fixed-point math
; We use 2 decimal places, so multiply by 100
DECIMAL_MULT equ 100
; *********************************************************************
; Program Entry Point
; *********************************************************************
org 32768 ; Start of code (above BASIC)
start:
call init_program ; Initialize program
main_loop:
; Display welcome message
ld de, welcome_msg
call print_string
; Get amount
call get_amount
; Store amount in amount_value
ld (amount_value), hl
; Get category selection
call get_category
; Store category in category_value
ld a, c
ld (category_value), a
; Calculate tax based on category and amount
call calculate_tax
; Store tax in tax_value
ld (tax_value), hl
; Calculate total
call calculate_total
; Store total in total_value
ld (total_value), hl
; Display results
call display_results
; Ask if user wants to continue
call ask_continue
cp 'Y' ; Check if user pressed 'Y'
jr z, main_loop ; If yes, repeat the main loop
cp 'y' ; Check if user pressed 'y'
jr z, main_loop ; If yes, repeat the main loop
; Exit program - return to BASIC
jp exit_program
; *********************************************************************
; Initialization
; *********************************************************************
init_program:
; Clear screen
call CLS
ret
; *********************************************************************
; Input Routines
; *********************************************************************
; Get decimal amount input from user
get_amount:
ld de, amount_prompt
call print_string
; Clear buffer
ld hl, input_buffer
ld (hl), 0
; Get input string
call get_input
; Convert string to number
ld de, input_buffer
call string_to_number
ret
; Get tax category from user
get_category:
ld de, category_prompt
call print_string
; Wait for key press
get_cat_wait:
call wait_key
cp 'F' ; Check for 'F'
jr z, cat_food
cp 'f' ; Check for 'f'
jr z, cat_food
cp 'H' ; Check for 'H'
jr z, cat_healthcare
cp 'h' ; Check for 'h'
jr z, cat_healthcare
cp 'O' ; Check for 'O'
jr z, cat_other
cp 'o' ; Check for 'o'
jr z, cat_other
; Invalid category, default to Other
ld de, invalid_cat
call print_string
ld c, 3 ; Category 3 (Other)
ret
cat_food:
ld c, 1 ; Category 1 (Food)
ret
cat_healthcare:
ld c, 2 ; Category 2 (Healthcare)
ret
cat_other:
ld c, 3 ; Category 3 (Other)
ret
; Ask if user wants to continue
ask_continue:
ld de, continue_prompt
call print_string
call wait_key ; Wait for key press
ret ; Return with key in A
; Wait for key press
wait_key:
ld hl, LAST_K ; Address of system variable LAST_K
ld (hl), 0 ; Clear key buffer
wait_loop:
ld a, (hl) ; Get key
or a ; Check if key pressed
jr z, wait_loop ; No key, continue waiting
ret ; Key pressed, return with key in A
; Get a string input from user
get_input:
ld hl, input_buffer ; Point to input buffer
ld b, 0 ; Input length counter
input_loop:
call wait_key ; Wait for key press
cp 13 ; Check for ENTER
jr z, input_done ; Input complete
cp 8 ; Check for DELETE
jr z, delete_char ; Handle delete
; Check if character is a valid digit or decimal point
cp '0'
jr c, input_loop ; Ignore if less than '0'
cp ':' ; ':' is after '9' in ASCII
jr c, valid_digit ; Valid digit
cp '.' ; Check for decimal point
jr nz, input_loop ; Ignore if not decimal point
valid_digit:
; Don't accept more than 6 characters
ld a, b
cp 6
jr nc, input_loop ; Too many chars
; Store character in buffer
ld a, (LAST_K)
ld (hl), a ; Store character
inc hl ; Next buffer position
inc b ; Increment counter
; Echo character to screen
rst 16
jr input_loop ; Continue input loop
delete_char:
; Handle backspace (delete) key
ld a, b
or a ; Check if buffer is empty
jr z, input_loop ; If empty, do nothing
; Move cursor back, erase character
ld a, 8
rst 16 ; Backspace
ld a, ' '
rst 16 ; Space (erase character)
ld a, 8
rst 16 ; Backspace again
; Update buffer
dec hl ; Move back in buffer
dec b ; Decrement counter
ld (hl), 0 ; Clear the deleted character
jr input_loop ; Continue input loop
input_done:
; Terminate string with null
ld (hl), 0 ; Null terminate
; Print newline
ld a, 13
rst 16
ld a, 10
rst 16
ret
; *********************************************************************
; Calculation Routines
; *********************************************************************
; Calculate tax amount based on category
; Input: amount_value (HL) and category_value (A)
; Output: tax in HL
calculate_tax:
ld a, (category_value)
cp 1 ; Check if Food
jr z, calc_food
cp 2 ; Check if Healthcare
jr z, calc_healthcare
; Default: Other category
calc_other:
ld a, OTHER_RATE ; 10% tax
jr do_tax_calc
calc_food:
ld a, FOOD_RATE ; 5% tax
jr do_tax_calc
calc_healthcare:
ld a, HEALTHCARE_RATE ; 0% tax
jr do_tax_calc
do_tax_calc:
; Tax calculation: amount * tax_rate / 100
; HL = amount
ld hl, (amount_value)
; Multiply by tax rate
ld b, 0
ld c, a ; BC = tax rate
call multiply ; HL = amount * tax rate
; Divide by 100
ld bc, 100
call divide ; HL = (amount * tax rate) / 100
ret ; Return tax in HL
; Calculate total amount (amount + tax)
; Input: amount_value and tax_value
; Output: total in HL
calculate_total:
ld de, (amount_value) ; DE = amount
ld hl, (tax_value) ; HL = tax
add hl, de ; HL = amount + tax
ret
; *********************************************************************
; Utility Routines
; *********************************************************************
; Convert string to number
; Input: DE points to null-terminated string
; Output: HL contains the number (fixed point, 2 decimal places)
string_to_number:
ld hl, 0 ; Initialize result to 0
ld b, 0 ; Decimal point flag (0 = not seen)
ld c, 0 ; Decimal position counter
str_to_num_loop:
ld a, (de) ; Get character
or a ; Check for null terminator
jr z, str_to_num_done
cp '.' ; Check for decimal point
jr z, decimal_point
; Must be a digit
sub '0' ; Convert ASCII to value
; Multiply current result by 10
push af ; Save digit
push bc ; Save flags
ld b, h ; BC = result
ld c, l
add hl, hl ; HL = result * 2
add hl, hl ; HL = result * 4
add hl, bc ; HL = result * 5
add hl, hl ; HL = result * 10
pop bc ; Restore flags
pop af ; Restore digit
; If past decimal point, increment decimal counter
push af ; Save digit
ld a, b
or a ; Check decimal flag
jr z, add_digit ; Not past decimal yet
; Past decimal point, check counter
ld a, c
inc c ; Increment counter
cp 2 ; Check if already have 2 decimal places
jr nc, skip_digit ; Skip this digit if already have 2 decimals
add_digit:
pop af ; Restore digit
ld e, a
ld d, 0 ; DE = digit
add hl, de ; Add to result
jr next_char
skip_digit:
pop af ; Restore digit (but don't use it)
jr next_char
decimal_point:
inc b ; Set decimal point flag
ld c, 0 ; Reset decimal position counter
jr next_char
next_char:
inc de ; Next character
jr str_to_num_loop
str_to_num_done:
; If no decimal part was entered, multiply by 100
ld a, b
or a ; Check if decimal point was seen
jr nz, adjust_decimals
; No decimal point, multiply by 100
ld b, h
ld c, l
add hl, hl ; * 2
add hl, hl ; * 4
add hl, hl ; * 8
add hl, hl ; * 16
add hl, hl ; * 32
add hl, hl ; * 64
add hl, bc ; * 65
add hl, hl ; * 130
ld b, h
ld c, l
srl b
rr c ; * 65
add hl, bc ; * 195
add hl, hl ; * 390
add hl, hl ; * 780
srl h
rr l ; * 390
add hl, hl ; * 780
srl h
rr l ; * 390
srl h
rr l ; * 195
srl h
rr l ; * 97.5
add hl, hl ; * 195
add hl, hl ; * 390
srl h
rr l ; * 195
add hl, hl ; * 390
srl h
rr l ; * 195
add hl, hl ; * 390
srl h
rr l ; * 195
add hl, hl ; * 390
srl h
rr l ; * 195
add hl, hl ; * 390
srl h
rr l ; * 195
add hl, hl ; * 390
srl h
rr l ; * 195
add hl, hl ; * 390
srl h
rr l ; * 195
jr done_convert
adjust_decimals:
; Check number of decimal places entered
ld a, c
or a ; No decimal places entered?
jr z, mul_100 ; Multiply by 100
cp 1 ; One decimal place entered?
jr z, mul_10 ; Multiply by 10
; Already have 2 decimal places, no adjustment needed
jr done_convert
mul_100:
; Multiply by 100
ld b, h
ld c, l
add hl, hl ; * 2
add hl, hl ; * 4
add hl, hl ; * 8
add hl, bc ; * 9
add hl, hl ; * 18
add hl, hl ; * 36
add hl, bc ; * 37
add hl, hl ; * 74
add hl, hl ; * 148
add hl, bc ; * 149
add hl, bc ; * 150
jr done_convert
mul_10:
; Multiply by 10
ld b, h
ld c, l
add hl, hl ; * 2
add hl, hl ; * 4
add hl, bc ; * 5
add hl, hl ; * 10
done_convert:
ret ; Return with number in HL
; Multiply HL by BC
; Input: HL, BC
; Output: HL = HL * BC
multiply:
push de ; Save DE
ld de, 0 ; Initialize result to 0
mult_loop:
; Check if BC is zero
ld a, b
or c
jr z, mult_done
; Add HL to result
ex de, hl ; DE = multiplier, HL = result
add hl, de ; Add multiplier to result
ex de, hl ; DE = result, HL = multiplier
; Decrement counter
dec bc
jr mult_loop
mult_done:
ex de, hl ; HL = result
pop de ; Restore DE
ret
; Divide HL by BC
; Input: HL, BC
; Output: HL = HL / BC
divide:
push de ; Save DE
ld de, 0 ; Initialize quotient to 0
div_loop:
; Check if HL < BC
call compare_hl_bc
jr c, div_done ; If HL < BC, done
; Subtract BC from HL
or a ; Clear carry
sbc hl, bc
; Increment quotient
inc de
jr div_loop
div_done:
ex de, hl ; HL = quotient
pop de ; Restore DE
ret
; Compare HL with BC
; Sets carry flag if HL < BC
compare_hl_bc:
ld a, h
cp b ; Compare high bytes
ret nz ; Not equal, return with flags set
ld a, l
cp c ; Compare low bytes
ret
; *********************************************************************
; Output Routines
; *********************************************************************
; Display calculation results
display_results:
; Display amount
ld de, amount_msg
call print_string
ld hl, (amount_value)
call print_number
; Display tax
ld de, tax_msg
call print_string
ld hl, (tax_value)
call print_number
; Display total
ld de, total_msg
call print_string
ld hl, (total_value)
call print_number
; Print a blank line
ld de, newline
call print_string
ret
; Print a string
; Input: DE points to null-terminated string
print_string:
ld a, (de) ; Get character
or a ; Check for null terminator
ret z ; If zero, we're done
rst 16 ; Print character
inc de ; Point to next character
jr print_string ; Continue printing
; Print a number in decimal format (fixed point, 2 decimal places)
; Input: HL = number (fixed point, 2 decimal places)
print_number:
; Print '$' sign
ld a, '$'
rst 16
; Extract the integer and fractional parts
; Integer part = number / 100
; Fractional part = number % 100
; Save the original number
push hl
; Divide by 100 to get integer part
ld bc, 100
call divide ; HL = integer part
; Print integer part
call print_decimal
; Print decimal point
ld a, '.'
rst 16
; Get fractional part
pop bc ; BC = original number
push hl ; Save integer part
; Calculate fractional part = original - (integer * 100)
push bc ; Save original number
ld bc, 100
call multiply ; HL = integer * 100
pop bc ; BC = original number
; BC - HL = fractional part
or a ; Clear carry
ld a, c
sub l
ld l, a
ld a, b
sbc a, h
ld h, a ; HL = fractional part
; Print fractional part as 2 digits
ld a, h
or a ; Check if high byte is zero
jr z, print_low_byte ; If zero, just print low byte
; Otherwise this is an error, print "00"
ld a, '0'
rst 16
rst 16
jr fract_done
print_low_byte:
ld a, l
cp 10 ; Check if less than 10
jr nc, print_fract ; If not, print as is
; Add leading zero for single-digit values
push af ; Save value
ld a, '0'
rst 16 ; Print leading zero
pop af ; Restore value
print_fract:
ld h, 0
ld l, a
call print_decimal
fract_done:
pop hl ; Restore HL
ret
; Print a decimal number in HL
print_decimal:
; Print a decimal number (0-65535)
ld bc, -10000
call num1
ld bc, -1000
call num1
ld bc, -100
call num1
ld bc, -10
call num1
ld bc, -1
num1:
ld a, '0'-1
num2:
inc a
add hl, bc
jr c, num2
sbc hl, bc
; Skip leading zeros, but always print at least one digit
cp '0'
jr nz, print_digit ; Not a leading zero
; Check if we already printed a digit
push af
ld a, (digit_printed)
or a
jr nz, print_ok
; Check if we're at the last digit
ld a, c
cp 255 ; Check if BC = -1 (last digit)
jr nz, skip_zero ; Not last digit, skip leading zero
print_ok:
pop af
jr print_digit
skip_zero:
pop af
ret
print_digit:
rst 16
; Set the digit_printed flag
ld a, 1
ld (digit_printed), a
ret
; *********************************************************************
; Exit Program
; *********************************************************************
exit_program:
; Display exit message
ld de, exit_msg
call print_string
ret ; Return to BASIC
; *********************************************************************
; Data Section
; *********************************************************************
; Prompts and messages
welcome_msg: defb 13, 10, "TAX CALCULATOR", 13, 10, 13, 10, 0
amount_prompt: defb "Enter purchase amount: ", 0
category_prompt: defb "Select tax category (F)ood, (H)ealthcare, (O)ther: ", 0
invalid_cat: defb 13, 10, "Invalid category. Using Other.", 13, 10, 0
amount_msg: defb 13, 10, "Amount: ", 0
tax_msg: defb 13, 10, "Tax: ", 0
total_msg: defb 13, 10, "Total: ", 0
continue_prompt: defb 13, 10, 13, 10, "Do you want to continue? (Y/N): ", 0
exit_msg: defb 13, 10, 13, 10, "Thank you for using Tax Calculator!", 13, 10, 0
newline: defb 13, 10, 0
; Variables
digit_printed: defb 0 ; Flag for printing leading zeros
; Input buffer (7 bytes: 6 chars + null terminator)
input_buffer: defs 7, 0
; Variables for calculated values (16-bit words)
amount_value: defw 0 ; Purchase amount
category_value: defb 0 ; Tax category
tax_value: defw 0 ; Calculated tax amount
total_value: defw 0 ; Total amount
; Program end
end start
Now we need to compile the application using pasmo --tapbas taxcalc.asm taxcalc.tap
. So let's give it a spin!

It works but the sales tax isn't being calculated properly so let's head on back and give the LLM the following prompt and attach the above screenshot to it.
Look at the attached screenshot.
The tax did not generate correctly.
The amount is accepting user input.
The cost is not being asked and user does not accept input.
Look at the @00-specs-without-c to see functionality needed. Make changes as needed.

which resolves the formatting issues, however user interactive input does not happen. The application should ask me how much an item costs but it does not do that. So let's head back to the LLM and attach the screenshot.

It took a few more backwards and forwards to steer it via screenshots that had verbose logging but success was achieved.

The techniques detailed below were able to take an application from assembler, generate high-level specs and those high-level clean-room specs were able to generate a working Z/80 spectrum application.

Anyway, that was a fun trip back in time and memory lane - thanks Damien Guard for the inspiration and nerd-snipe. In all it took me about two hours, of that it was mainly me trying to figure out how to install, run, configure these emulators and how to make a Z/80 tape.
The actual build was all very manual, driven by me as a human loop because the the Z/80 spectrum isn't really a good target for automating a reward loop. These LLMs get most things right but some things wrong. By having tests in your codebase and invoking the MCP tools to run these tests you'll get successful outcomes automatically nine times out of ten...
It's a new world for software engineers, it's kinda like the Telsa FSD. Sometimes it gets it wrong but if you trust it enough, provide enough guidance and understand how these coding assistants work under the hood you can achieve N factor more outcomes than ever before.
Software rewrites from one language to another are no longer an expensive thing for a company to do and if a "source available" company has provided the internet with their restricted source-code they can be cloned in hours, automatically, whilst you watch Netflix.
ps. socials for this blog post are below
If you enjoyed reading, give 'em a share please: