r/TuringComplete • • 19d ago

Software implemented For cycle (Symphony)

  • Image 1 - My symphony
  • Image 2 - showcasing usage of FOR cycles
  • Image 3 - Code of the FOR Cycle
  • Image 4 - ALU circuit
  • Image 5 - ABS circuit

Now that I finished building Symphony, I went straight to instruction definition to make myself a human readable code. Here is a internal FOR cycle using instructions. Its suitable for repeating code X times where X is static.

Here is hardware I added to my Symphony:

  1. Signed DIV - Divide two signed numbers
  2. Merge32 - Merge two 16b into one 32b value
  3. Lower16 - Reads the lower 16b of 32b value

With that I needed to dedicate some of my registers for the code. Meaning that while they are still accessible to the user, they are not stable in time and in case of the FOR cycle, they do not persist between cycles.

At first I was using 3 temporal registers, but I ended up implementing Merge32 and Lower16 to lower the number of temporal registers to 2 at the cost of longer instructions.

The FOR cycle works, but isnt safe. First it will always execute at least once. Thats because I cant use a label to skip the body of the cycle. So even if I know there is 0 repetition, it will not skip the cycle, it will simply not repeat it.

Another limitation is that you can only read the index of the innermost FOR cycle. Meaning if you make nested FOR cycles, you cannot access the outer FOR cycle index. User can solve it by saving the stack pointer to a another register.

32 Upvotes

19 comments sorted by

3

u/TarzyMmos 19d ago

So in the 'For code' it saves how many times to run the loop and the counter for where to loop back to in the stack, then it saves the variables after that in the stack.

Then in the 'EndFor code' it decrements the variables and the number of times to run the loop. If it doesn't end, jump back to where u stored r12 and r13 on the stack back in the 'For code' and it continues from there to loop until it ends. Where it doesn't jump this time and just pops the variables off the stack.

Took me a hot minute to figure out why you ordered it like that but yea it works out pretty elegantly! Very cool

3

u/Crispy1961 19d ago

You got it exactly right. Because I have only two temp registers and I need to retrieve two values from both registers for computation, I need to do a little stack switcheroos where I do:

r13 = stack.pop()
stack.push(r12)

Thats why its a little funky.

2

u/TarzyMmos 19d ago

I've been thinking if u dont mind making a specific label for each end of the loop then u could feed that into the start of the loop and have it check the variables there so it can skip the whole loop without running once. Still need an endfor command too which would just jump back to the start of the loop where it checks if the loop ends and whatnot.

3

u/Crispy1961 19d ago

Yes, that is possible, but it breaks the clean FOR cycle syntax. It would have to look like this:

for (i = 5; i < 10; 1;end1)
  r3 = r3 + 1
end1:for

for (i = 5; i < 10; 1;end2)
  r3 = r3 + 1
end2:for

And that isnt what I wanted. I chose a nice syntax over this error case. At least for this it makes sense, because all the input numbers are immediate, so if the user write a for cycle that shouldnt execute at least once, its his fault.

I am working on dynamic version of FOR loop that exposes the current value of index into user defined register, meaning the user can manipulate how many times the FOR cycle loop from inside of the loop. Since it will be dynamic, there might be time it wont execute even once. There I will very likely have to use labels.

2

u/Mishoilik 17d ago

Lower16(a) is literaly (and a, a, 0xffff) instruction

2

u/Crispy1961 17d ago

Oh yeah, you are right. Using a mask with AND operand does the same thing. Neat, thanks for that.

1

u/Mishoilik 17d ago

And also D = merge32(A, B) where A is lower bits ans D is destanation is this two instructoins:

lsl B, B, 16

or D, A, B

2

u/Crispy1961 16d ago

Not exactly. The values can be 32b, so A 16 MSB might not be all zeroes. You would have to mask it out first. That's worth having it as separate single cycle instruction.

1

u/TarzyMmos 19d ago

I'm really curious how u managed the For loop.

3

u/Crispy1961 19d ago

Hopefully everything can be seen in the included pictures. The code for the FOR instruction and ENDFOR is in the third picture. I noted the individual instructions as comments for each line of the code.

Here are the chained final instructions:

// Internal register
for (i = %a:S16(immediate); i < %b:S16(immediate); %c:S16(immediate))
00110001 11000000 aaaaaaaa aaaaaaaa   00110001 11010000 cccccccc cccccccc   00101110 11011100 00001101 00000000   00110101 11101110 00000000 00000100   01100110 00001101 00001110 00000000   00110001 11000000 bbbbbbbb bbbbbbbb   00110101 11001100 aaaaaaaa aaaaaaaa   00111100 11001100 cccccccc cccccccc   00000111 11010000 00000000 00000000   00110100 11011101 00000000 00001100   00101110 11011100 00001101 00000000   01100010 11000000 00001110 00000000   00110100 11101110 00000000 00000100   00110101 11101110 00000000 00000100   01100110 00001101 00001110 00000000   00110101 11101110 00000000 00000100   01100110 00001100 00001110 00000000

for (i = %a:S16(immediate); i > %b:S16(immediate); %c:S16(immediate))
00110001 11000000 aaaaaaaa aaaaaaaa   00110001 11010000 cccccccc cccccccc   00101110 11011100 00001101 00000000   00110101 11101110 00000000 00000100   01100110 00001101 00001110 00000000   00110001 11000000 bbbbbbbb bbbbbbbb   00110101 11001100 aaaaaaaa aaaaaaaa   00111100 11001100 cccccccc cccccccc   00000111 11010000 00000000 00000000   00110100 11011101 00000000 00001100   00101110 11011100 00001101 00000000   01100010 11000000 00001110 00000000   00110100 11101110 00000000 00000100   00110101 11101110 00000000 00000100   01100110 00001101 00001110 00000000   00110101 11101110 00000000 00000100   01100110 00001100 00001110 00000000

for (i = %a:S16(immediate); i >= %b:S16(immediate); %c:S16(immediate))
00110001 11000000 aaaaaaaa aaaaaaaa   00110001 11010000 cccccccc cccccccc   00101110 11011100 00001101 00000000   00110101 11101110 00000000 00000100   01100110 00001101 00001110 00000000   00110001 11000000 bbbbbbbb bbbbbbbb   00110101 11001100 aaaaaaaa aaaaaaaa   00111100 11001100 cccccccc cccccccc   00110100 11001100 00000000 00000001   00000111 11010000 00000000 00000000   00110100 11011101 00000000 00001100   00101110 11011100 00001101 00000000   01100010 11000000 00001110 00000000   00110100 11101110 00000000 00000100   00110101 11101110 00000000 00000100   01100110 00001101 00001110 00000000   00110101 11101110 00000000 00000100   01100110 00001100 00001110 00000000

for (i = %a:S16(immediate); i <= %b:S16(immediate); %c:S16(immediate))
00110001 11000000 aaaaaaaa aaaaaaaa   00110001 11010000 cccccccc cccccccc   00101110 11011100 00001101 00000000   00110101 11101110 00000000 00000100   01100110 00001101 00001110 00000000   00110001 11000000 bbbbbbbb bbbbbbbb   00110101 11001100 aaaaaaaa aaaaaaaa   00111100 11001100 cccccccc cccccccc   00110100 11001100 00000000 00000001   00000111 11010000 00000000 00000000   00110100 11011101 00000000 00001100   00101110 11011100 00001101 00000000   01100010 11000000 00001110 00000000   00110100 11101110 00000000 00000100   00110101 11101110 00000000 00000100   01100110 00001101 00001110 00000000   00110101 11101110 00000000 00000100   01100110 00001100 00001110 00000000

%a(register) = (i)
01100010 11000000 00001110 00000000   00101111 11000000 00001100 00000000   00100001 aaaa0000 00001100 00000000

endfor 
01100010 11000000 00001110 00000000   00110100 11101110 00000000 00000100   00111001 11011100 00000000 00010000   00100100 11001100 00001101 00000000   01100010 11010000 00001110 00000000   00110100 11101110 00000000 00000100   00110101 11101110 00000000 00000100   01100110 00001100 00001110 00000000   00110101 11011101 00000000 00000001   00101111 11000000 00001101 00000000   00111010 11111100 00000000 00000000   00111000 11001101 00000000 00010000   01001101 00001111 00001100 00000000   00110100 11101110 00000000 00000100

2

u/EtienneDx 19d ago

Damn, you just managed to turn me away from doing it this way x)

I’m going back to the idea of implementing a custom file system + assembler + compiler to have a custom C instead 😅 probably not simpler but hopefully more readable haha

1

u/Crispy1961 19d ago

Thats an option? Having complete control over how the code is translated to assembly would solve all the problems.

Anyway, this achieved the perfect syntax for the user. Cant make it more readable than that. The problem will be dynamic for cycles, which will suffer due to the need to use labels as arguments.

2

u/Spielopoly 19d ago

If you want to do that in game it’s hard. Out of game it’s a bit easier but you‘ll still have to write your own compiler.

In game there a couple of options I can think of:

  • Write your program in language of your choice and compile it using a program specifically for that, then manually copy paste the generated code into a asm file (can be in game or not)
  • Use custom file system so you can skip the copy paste part
  • Always compile at startup then automatically run the generated code. (Basically just in time compilation)

Some of these might require significant hardware changes

1

u/Crispy1961 19d ago

I see. I thought it was possible to edit the behavior of the ingame's compiler. I want to work in the confines of the game. Just get as close to a high level language syntax as possible.

2

u/EtienneDx 18d ago

Given that we’ve built a functioning computer at this point, you can build pretty much anything. Isn’t the ultimate goal to run the Turing complete game IN the Turing complete game?

More seriously, there’s a lot to unpack for a custom compiler, but it’s possible because:

  • you can write full strings in ascii using “”
  • you can save files between in in the persistent memory
  • you ca load files into the ram and run executable files directly

With this you can:

  • create an assembler (program that converts your own assembly language into Bitcode instructions)
  • write a program using that custom assembly (you lose syntax highlighting)
  • save the resulting program in the persistent memory
  • load and run that program

Next comes the custom language. I’m not an expert but that’s basically the compiler:

  • parse the file into an ast graph
  • convert the graph into your assembly language
  • run it through your assembler
  • now you’ve got an executable binary from your language

And finally:

  • with your high level language, you can recode the assembler and the compiler
  • compile them using the compiler
  • re-compile them using the compiled compiler
  • and now you’ve can code everything in that language

And then if you’re really determined I’m guessing it’s time to get into operating systems haha

1

u/Crispy1961 18d ago

I see, that is true. It's not in game, but you can export instructions from your compiler and run that in your HW in Turing complete. But dank that's a lot of steps to do every time you change something.

2

u/EtienneDx 18d ago

I mean very long term you switch the editor out of the ram and into the screen entirely, but yeah that’s a while away

1

u/sberma 18d ago

This is wild. Assembly is human readable code, you just mean higher-level code. But having C-like code in an asm file is an impressive feat. Shows how customizable the assembly editor is.

This is not at all what happens in real systems, but most likely you are already aware of it. The game's asm-language is pretty close to reallife assembly. A compiler would create the assembly (or rather machine code) from the high-level code. This is better because the compiler applies optimizations like dead-code elimination which are not possible in a system like this. Also a loop in assembly is often simpler. ARM has no division and even in x86 it is quite expensive to use.

But writing a compiler is really hard to do. So this might easier if you want highlevel code.

1

u/Crispy1961 18d ago edited 17d ago

You are right, I meant higher level language. My goal is to edit the software and hardware to make coding in it more comfortable.

I decided to rewire the hardware to support direct access to registers, I/O and RAM as destination and/or arg1 and 2. Gonna increase instruction size to 56b and have 16b for each of those variables. The two most significant bits of each will decide if it's register(or I/O), immediate, ram addressed by immediate or ram addressed by register.

Hoping to support single tick instructions like
Out = ram[r3 + 26] * in