v50 Steam/Premium information for editors
  • v50 information can now be added to pages in the main namespace. v0.47 information can still be found in the DF2014 namespace. See here for more details on the new versioning policy.
  • Use this page to report any issues related to the migration.
This notice may be cached—the current version can be found here.

User:Larix/Programming the Maple 6-6

From Dwarf Fortress Wiki
Jump to navigation Jump to search

I'll try to give a more detailed account of how the machine language and in specific case the sample program works.


The Instructions[edit]

This is the reference table:

% l t u p z
1 N O P L A 1 D - > C N A N D X O R A - > B
2 N O P L A 2 B - > C A N D N X O R D E C
3 N O P L A 3 C - > B N A N D X O R I N C
4 N O P L A 4 D - > C A N D N X O R A - > B
5 N O P S H F B - > C N A N D X O R D E C
6 J M P N Z N O P C - > B A N D N X O R I N C
- > B - > D I N C / D E C ( A )

Each logical and transfer command has its own set of four mechanical gates outputting signals via minecart/roller power-to-signal converters. The gates are operated through signals received from the "input" registers, and these inputs are effectively always on. In order for the desired command (and no other) to be executed, all operation sets are connected to main power through a switchable gear assembly that is only (and only shortly) connected when the command in question is requested by the program. Furthermore, for every command that requires a writing operation, this "write" must also be triggered specifically - through another power-to-signal converter that's only powered for a very short time.

%[edit]

The % command is "encoded" by the absence of a minecart in the code loop. There's a mandatory wait period of about 110 steps between commands (otherwise, signals would overlap and produce garbage) and this period is enforced by the code cart's passage over a plate, which disables the next roller on the counting track for the needed time. If there's no cart in the code loop, the signal is not sent and the roller is not switched off. Since in a NOP, no code is executed, this poses no problem and means that a NOP is pure space padding and doesn't consume a significant amount of time.

In each sixth position, the code cart's passage connects an additional roller actually keeping the counter cart on its course. If the roller is off, the cart is directed six steps back. Alternatively, the roller is powered when the "B" register's contents are equal to zero. The "B" register's contents are checked by a four-step "NOR" gate (four gear assemblies in line connected to the plates in the B register's cells - disconnected when the plate is on), and if this gate returns true, a signal is sent that connects the jump roller. The jump takes place when the roller is off, and the roller is only off if there's no cart in the last program location and if the "is Register B zero" check returns false.

l[edit]

The "l"oad commands one to four set one bit of register B to true. This is done by sending both a data and enable signal to the exact bit in question. "l" at 5 takes the states of bits B1 - B3 and sends them to bits B2 - B4, through a simple three-roller array. The pre-shift state of B4 is dropped and a zero will be written to B1. The command activates both the "copy shifted state back into B" and the "write to B" rails. This double activation is required for all other transfer/logic commands, as noted above, and will not be mentioned explicitly again. Since a NOP should be an option in every position, l takes over that role in position six. No actual operation takes place, but since this NOP is triggered by a cart, the 120-step delay is enforced.

t[edit]

The "t"ransfer commands are operationally quite simple - three rails, each of which takes the state of one register and, if activated, sends a signal to another.

u[edit]

"u" (UND) is the first bank of binary logic operations. The two possible functions AND and NAND require two separate logic arrays, although both take the same inputs and send output to the same adress - AND toggles to NOR and NAND to OR in mechanical logic.

Both commands write to the same register, and use the same "write to" trigger as the bit-shift and "transfer C->B" and "transfer A->B" commands.

p[edit]

The "p"arity functions can be run through the same logic rail - an additional toggle signal to the XOR gear converts it to NXOR, and this toggle can be taken directly from the code cart.

z[edit]

The "z"ähler commands use a dedicated incrementer/decrementer. This was possibly the most complex single component. It might be possible to come up with a compacter design, but it was the best i was capable of: this is a fully mechanical adder based on Jong's groundbreaking work, calculating both the carries and their inverse to allow an instant result output. It's obviously a lot less complicated than a proper full adder, since one of the inputs is a constant One. By toggling the requisite gears, it can both add and subtract.

The third option is simply a content copy from the counter register to register B, so the count can be checked against zero. This allows to build conditional loops based on simple counts.


The test program: Multiplication[edit]

The counter must be set to the second factor of the multiplication minus one; the program implicitly assumes that this factor is at least two, smaller factors result in negative overflow and consequently, overlong operation with a faulty result (because it erroneously calculates 17x3 instead of 1x3).

BEGIN SETUP

A18 % # NOP

A19 l # set bit B1 (B = 0001)

A20 l # set bit B2 (B = 0011 - Factor one (F1) of the multiplication)

A21 % # NOP

A22 % # NOP

A23 t # copy B to C (C = 0011) END OF SETUP (this effectively already does F1x1)

A24 t # START OF MAIN LOOP copy C to B (B = C, 0011 on first pass, very different values in later rounds

B1 u # B NAND C, write to B (both started out equal, so B is the inverse of C now)

B2 u # B AND C, write to B (B = 0000; A24, B1, B2 do "set B to zero")

B3 % # NOP

B4 % # NOP

B5 % # NOP

B6 l # NOP (position six, NOP is done through "l"oad, although % would also work, since B is guaranteed to be zero)

B7 l # set bit B1 (B = 0001)

B8 l # set bit B2 (B = 0011) F1 entered into register B

B9 % # NOP

B10 % # NOP

B11 % # NOP

B12 l # NOP (see B6, but now, % would result in an endless loop) BEGIN ADDITION LOOP

B13 p # B XOR C, write to D

B14 u # B AND C, write to B

B15 % # NOP

B16 t # copy register D to C

B17 l # shift B one position left

B18 % # jump back to B12 if reg. B not zero END ADDITION LOOP

B19 % # NOP

B20 z # decrement reg. A (counter)

B21 % # NOP

B22 z # copy reg. A to B

B23 % # NOP

B24 % # jump back to A24 if B not zero END OF MAIN LOOP

C1 %

C2 %

END OF TAPE

SETUP just ensures a proper starting state independent of potential lingering data in reg. C. If register C is set to zero manually before running the program, it could simply be run from position A24, cutting out the SETUP. In that case, the counter should be set to the actual value of the second factor, not factor2 minus one. MAIN LOOP: Factor one must be brought into register B. This is done by first setting register B to zero (A24-B2), then directly loading the number into it (B7-B10). After that, the ADDITION LOOP adds the contents of B to those of C and stores the result (running sum) in C. ADDITION LOOP: this runs the von-Neumann algorithm of addition. First, the sums and carries at each bit are calculated, sums by bitwise XOR, carries by bitwise AND. The intermediate sum is stored back into register C for the next pass, the carries (in register B) shifted up one position. Then the process is repeated until no more carries are generated (jump back to the start of the loop if B not zero). After the end of addition, the counter is decremented by one and the result checked. If it's different from zero, operation jumps back to the start of the MAIN LOOP. If the counter is zero, the multiplication is finished.