Logic Gates in OpenTTD

| tags: code game

Backdated content; see this post for details.

Here's a rather old (and probably outdated) look at how one could simulate digital logic circuits with OpenTTD. Includes the fastest four-bit ripple-carry adder ever: takes about two months (of in-game time) for the carry information to propagate.

Introduction

This page introduces some futile attempts to simulate digital logic circuitry with OpenTTD.

Single-track Logic

Theory

Very simple. A track signaling segment has the logical value of 1 if it occupied, 0 if not. Input signals come in via a single track, and so do outputs. Advantages: less tracks, less clutter, less complex; disadvantages: as far as I've been able to figure out, needs the clocking signals to tell the circuits when to move.

The not/nand Case

Following pictures are screen captures of the not and nand functions. The railway segments marked with signs A, B are the inputs, not A and A nand B are the outputs (there are signals placed to show the value). The Wclk lines (unintuitively) control reading the inputs: set it to 0 for a sufficiently long time, and the circuits assume the correct output values. The Rclk lines (combined in the picture) are used to reset the gates: set to 0 after the outputs have been read.

A = 0, B = 0: not A = 1, A nand B = 1
  1. The initial state of the network: a0b0_init.png.
  2. When the Wclk lines are set to 0, both trains (+nand and +not) start to move: a0b0_moving.png.
  3. After a suitably long time has passed, both trains end up at their respective waypoints, and we get the results: both outputs have a red singal, thusly the value 1: a0b0_final.png.
  4. When the common Rclk line is set to low, the trains go back to their waiting positions; first +nand: a0b0_resetting_1.png, then +not: a0b0_resetting_2.png.
A = 1, B = 0: not A = 0, A nand B = 1
  1. Initial state: a1b0_init.png.
  2. When we toggle the Wclk lines, we see that only the +nand train moves. We end up with the results: a1b0_final.png.
A = 1, B = 1: not A = 0, A nand B = 0
  1. Initial state: a1b1_init.png.
  2. Set the Wclk lines to 0. Neither of the trains move: a1b1_final.png.
  3. Turning on the Transparent buildings option helps visibility: a1b1_trans.png.

Double-track Logic

Theory

Signals are carried with two tracks. Occupation of one track signifies a 0, occupation of the other is 1. The value is undefined if both or none of the tracks are occupied. Advantages: no need for clock signals; disadvantages: takes up a lot of space (due to bridge-crossings), complexity increases.

nor Prototype

The pictures contain a very crude nor gate. It seems to require the NPF (New Pathfinding) patch found in newest OpenTTD versions. (Not in the 0.4.0.1 release, though, or so I've read.) The are lots of bridges, mainly because lots of connections are required to release the train after the state of the input lines change.

Let's take a closer look at it, shall we?

  • A = 0, B = 0; A nor B = 1: l2_a0b0_init.pngl2_a0b0_final.png. Both 0 lines (A0, B0) are occupied, so the +nor train passes straight through and ends up waiting at the pre-signal after that one bridge, occupying the (A nor B)1 track. If either of the lines A0, B0 clear, the train proceeds to the _nor waypoint, and reads the inputs again.
  • A = 0, B = 1; A nor B = 0: l2_a0b1_init.pngl2_a0b1_final.png. Instead of B0, now the track B1 is occupied, causing the train to turn right at the later intersection. It ends up waiting at the far-right signal, and occupies (A nor B)0 instead. if A0 or B1 clear, the train proceeds back to _nor.
  • A = 1, B = 0/1; A nor B = 0: l2_a1b0_init.pngl2_a1b0_final.png; l2_a1b1_init.pngl2_a1b1_final.png.. In case the A1 track is occupied, the train turns right at the first intersection already. Then it waits for A1 to clear.

Generic Two-input Logic Gate

Now here's an useful construction: a generic two-input logic gate. You have at the Output switchboard 4 tracks, labeled An,Bm (where n, m ∈ {0, 1}), for both the Out=0 and Out=1 output lines. Then you just connect the desired input combinations to either one of the lines. A nand gate is pictured in the image.

Obviously it is self-evident how it works, but just for the record: the active train starts at the _gate waypoint, forks first according to A, then according to B, and ends up in one of the spots labeled A=n,B=m. There it waits for either one of the selected lines A=n and B=m to clear. When one does, the train loops back to _gate.

The image is rather big: a 1450x708px PNG. gate.png. In the picture both inputs are 0, so the train waits at A=0,B=0 and Out=1 is occupied.

The 4-bit Adder

Using the previous two-input gate, it is relatively easy to build a four-bit ripple-carry adder. The inputs are An for the four-bit number A, Bn for B, and output signals are Sumn for the four-bit sum, and C for carry. The circuit looks like this:

Half adder

[Sum, C] = hadd(A, B) {
  Sum = A xor B
  C = A and B
}

Full adder

[Sum, Cout] = fadd(A, B, Cin) {
  [sum1, c1] = hadd(A, B)
  [S, c2] = hadd(sum1, Cin)
  Cout = c1 or c2
}

4-bit ripple-carry adder

[Sum0, Sum1, Sum2, Sum3, C] = 4add(A0, A1, A2, A3, B0, B1, B2, B3) {
  [Sum0, c0] = hadd(A0, B0)
  [Sum1, c1] = fadd(A1, B1, c0)
  [Sum2, c2] = fadd(A2, B2, c1)
  [Sum3, C] = fadd(A3, B3, c2)
}

Putting this all together, we get a really ugly diagram.

Obviously this can be realized with 17 copies of the two-input logic gate described earlier. Which is just what I did, after adding a copy-paste kludge to OpenTTD. It does work, although it takes about two months of game-time for the carry information to ripple down. There is a picture of it, but it's... rather large: 3.3 MB, 9136 x 5504 pixels. ttd_4adder.png.

Conclusions

OpenTTD is not really a very good platform to simulate digital logic circuits on. Thanks to the pre-signals, it's infinitely better than original TTD (or TT), but it's still not very good. Obviously it is possible, though.