Designing a computer from the ground up in Wireworld

Daniel Sánchez, WRSA

#WolframTechConf

What is Wireworld?

A two-dimensional cellular automaton suited for simulating digital logic

Created by Brian Silverman around 1984

First described in the book “The Phantom Fish Tank” (1987)

What is Wireworld?

Operates on an infinite square grid where each cell exists in one of four distinct states:

Empty/Blank (0): represents blank space ()

Electron Head (1): the active “head” of an electron signal ()

Electron Tail (2): the trailing end of an electron signal ()

Wire/Copper (3): conducts electrons and forms the circuitry ()

With[{lblStyle=Medium},
GraphicsRow[{
Labeled[,"Blank",LabelStyle->lblStyle],
Labeled[,"Copper",LabelStyle->lblStyle],
Labeled[,"Head",LabelStyle->lblStyle],
Labeled[,"Tail",LabelStyle->lblStyle]
}]
]

The Wireworld computer

WireworldPlot[Wireworld[],ImageSize->500,Frame->None]

DynamicModule[{state=primeComputer,gen=0,inc=72},
Dynamic[
gen+=inc;
Panel[WireworldPlot[state=WireworldEvolve[state,{{inc}},Method->"Library"],ImageSize->Medium,Mesh->None,Frame->None],Style[Row[{"Generation: ",gen}],Medium]]
]
]

The rules of Wireworld

In each generation, cells update based on its current value and its eight neighboring cells (a.k.a Moore neighborhood)

Empty () → Empty ()

Never changes—acts as inert background

Electron Head () → Electron Tail ()

Electron heads always turn into electron tails

Electron Tail () → Wire ()

Electron tails always turn into wire cells

Wire () → Electron Head ()

Only if exactly 1 or 2 neighboring cells are Electron Heads

Otherwise, remains unchanged

Paclet[“DanielS/Wireworld”]

I’ll be using the Wireworld paclet I wrote a couple of years ago:

Paclet[“DanielS/Wireworld”]

Install the paclet:

PacletInstall["DanielS/Wireworld", UpdatePacletSites -> True]

Load the paclet:

<< DanielS`Wireworld`

?DanielS`Wireworld`*

Each symbol has a documentation page:

WireworldDraw

WireworldDraw[SparseArray[]]

WireworldDraw[Wireworld[]]

Electrons

An electron consists of a head and a tail

Along a line of wire cells, it will propagate in the direction pointed by the head at the rate of one cell per generation

Example:

A straight line of copper wire cells ()

A single electron head-tail pair (—) placed on the wire

GraphicsColumn[MapIndexed[Labeled[WireworldPlot[#],#2[[1]]-1,Left]&,WireworldEvolve[SparseArray[],6]],Spacings->0]

ListAnimate[
MapIndexed[Labeled[WireworldPlot[#1],#2[[1]]-1,Right,LabelStyle->Large]&,WireworldEvolve[Wireworld[],9]],SaveDefinitions->True]

Electrons

Electrons can turn around corners:

GraphicsRow[WireworldPlot/@WireworldEvolve[SparseArray[],4]]

Electrons

An electron caught in a loop will go around it forever:

ListAnimate[WireworldPlot/@WireworldEvolve[SparseArray[],23],]

Signals

Wires serve as a way to transport sequences of electrons from one location to another

We can encode data represented by electron presence/absence in wire segments of equal length (n)

‘1’ = electron at segment start; ‘0’ = empty segment

Requires synchronized sender and receiver

Manipulate[
WireworldPlot[SparseArray[],Mesh->{None,Range[0,20,n]},MeshStyle->Yellow],
{{n,2},Range[1,6],ControlType->Slider}
]

Segment size

Systems using a specific segment length are denoted as n-micron technology

Segments must be large enough to contain both an electron head and tail (minimum n=2)

Practical operation demands at least one empty cell between electrons, making n=3 the actual minimum

GraphicsColumn[WireworldPlot/@WireworldEvolve[SparseArray[],3],Spacings->0]

Segment size

All necessary computer logic can be implemented in 3-micron technology:

AnimatedImage[Rasterize/@(WireworldPlot[#,Mesh->{None,Range[0,21,3]},MeshStyle->Yellow]&)/@WireworldEvolve[SparseArray[],12],FrameRate->1,ImageSize->Medium]

Because of simplicity and compactness, we are going to use the more conservative 6-micron technology:

AnimatedImage[Rasterize/@(WireworldPlot[#,Mesh->{None,Range[0,21,6]},MeshStyle->Yellow]&)/@WireworldEvolve[SparseArray[],12],FrameRate->1,ImageSize->Medium]

Signals (example)

In the following configuration, the loop on the left generates a new electron every 6 simulation steps

Note the consistent 6-cell spacing between successive electron heads:

Module[{list},
list=MapIndexed[Labeled[WireworldPlot[#1],StringPadLeft[ToString[#2[[1]]-1],2," "],Left,LabelStyle->Medium]&]@WireworldEvolve[SparseArray[],12];
Partition[list,4]//Transpose//GraphicsGrid[#,ImageSize->550,Spacings->0]&
]

Signals (example)

Below are examples of 6-micron signals

The top wire transmits a continuous stream of zeros

The middle wire carries a constant sequence of ones

The bottom wire repeats the pattern ‘101’ indefinitely

ListAnimate[MapIndexed[Labeled[WireworldPlot[#1],#2[[1]]-1,LabelStyle->Large]&,WireworldEvolve[SparseArray[],35]],SaveDefinitions->True]

Controlling electron emission

There exists a connection between the loop size in our previous example and the timing of electron emissions

The emission interval is equal to the loop’s total length

Length determines timing

A loop with length n emits electrons every n steps

ListAnimate[MapIndexed[Labeled[WireworldPlot[#1],StringPadLeft[ToString[#2[[1]]-1],2," "],Right,LabelStyle->Large]&]@WireworldEvolve[SparseArray[],59],SaveDefinitions->True,AnimationRate->1.5]

Shape independence

Only the total cell count matters—the loop’s geometry is irrelevant

ListAnimate[MapIndexed[Labeled[WireworldPlot[#1],StringPadLeft[ToString[#2[[1]]-1],2," "],Right,LabelStyle->Large]&]@WireworldEvolve[SparseArray[],59],SaveDefinitions->True,AnimationRate->1.5]

Practical limitation

Loops explain why creating a reliable 3-step emitter is challenging

The minimum loop length must accommodate electron heads/tails

Can you design a functional 3-interval emitter?

Below is a working 4-interval emitter:

ListAnimate[MapIndexed[Labeled[WireworldPlot[#1],StringPadLeft[ToString[#2[[1]]-1],2," "],Right,LabelStyle->Large]&]@WireworldEvolve[SparseArray[],24],SaveDefinitions->True,AnimationRate->1.5]

The diode

A diode allows the flow of electrons in only one direction:

WireworldPlot[SparseArray[]]

An electron flowing the “right” way through a diode (electrons come from the top):

GraphicsRow[WireworldPlot/@WireworldEvolve[SparseArray[],6]]

An electron flowing the “wrong” way through a diode (electrons come from the bottom):

GraphicsRow[WireworldPlot/@WireworldEvolve[SparseArray[],6]]

How diodes work

A copper cell turns into an electron head if it borders exactly one or two electron heads

Therefore, a copper cell stays unchanged if three (or more) neighboring cells are electron heads

When an electron flows through the diode, it eventually reaches a state where three cells in a column simultaneously become electron heads

ListAnimate[MapIndexed[Labeled[WireworldPlot[#1],#2[[1]]-1,LabelStyle->Large]&]@WireworldEvolve[SparseArray[],5],SaveDefinitions->True]

Diodes are rarely used in practice since bidirectional communication on a single wire is uncommon

The OR gate

The OR gate is the most fundamental logic component in Wireworld

Below is a demonstration of its operation with all four possible input combinations:

ListAnimate[
MapIndexed[Labeled[WireworldPlot[#1,PlotRange->{{4,10},{15,30}}],#2[[1]]-1,LabelStyle->Large]&]@
WireworldEvolve[SparseArray[],23],
SaveDefinitions->True,
AnimationRate->1.5
]

The OR gate component consists of five copper cells arranged in a plus-sign configuration

The OR gate

There is one subtle (but crucial) design feature — an extra placed cell on the left arm

This extra cell serves the critical function of preventing signal backflow

ListAnimate[Column/@Transpose@Map[WireworldPlot[#1,PlotRange->{{4,10},{15,30}}]&,WireworldEvolve[#,23]&/@{SparseArray[],SparseArray[]},{2}],
SaveDefinitions->True,
AnimationRate->1.5
]

Without it, a signal arriving at one input would propagate backward through the inactive input wire (most likely causing destructive interference with incoming electrons)

The exclusive-OR gate

A 6-micron XOR gate implementation:

ListAnimate[
MapIndexed[Labeled[WireworldPlot[#1,PlotRange->{{3,11},{16,30}}],#2[[1]]-1,LabelStyle->Large]&]@
WireworldEvolve[SparseArray[],23],
SaveDefinitions->True,
AnimationRate->1.5
]

Uses the same input timing sequence as the earlier OR gate example

The electrons entering the XOR gate at generation 18 are not propagated to the right (as designed)

TableForm[BooleanTable[A⊻B,{A},{B}]//Boole,TableHeadings->{{"B=0","B=1"}, {"A=0","A=1"}}]

The AND-NOT gate

Calculates the function A && !B

TableForm[BooleanTable[A&&!B,{A},{B}]//Boole,TableHeadings->{{"B=0","B=1"}, {"A=0","A=1"}}]

One possible implementation of this gate is the following:

ListAnimate[
MapIndexed[Row[{Column[{"B","A"},Spacings->5],Labeled[WireworldPlot[#1,PlotRange->{{3,11},{16,30}}],#2[[1]]-1,LabelStyle->Large],Column[{"A && !B","A || B"},Spacings->1]}]&]@
WireworldEvolve[SparseArray[],23],
SaveDefinitions->True,
AnimationRate->1.5
]

The secret to how this gate works are the small copper “ears” — single extra cells sticking out near the outputs

When an ear is present, that output calculates the AND-NOT function

Without an ear, it calculates the OR function instead

When you add ears to both sides, the gate becomes a “broken crossover” (the top output being A && !B, while the bottom one is B && !A)

This lets two signals cross paths safely, as long as they’re never both ‘1’ at exactly the same time (look what happens at generation 18):

ListAnimate[
MapIndexed[Labeled[WireworldPlot[#1,PlotRange->{{3,11},{16,30}}],#2[[1]]-1,LabelStyle->Large]&]@
WireworldEvolve[SparseArray[],23],
SaveDefinitions->True,
AnimationRate->2
]

Other implementations

The previous implementation is not the only way of setting up an AND-NOT gate in Wireworld

ListAnimate[
MapIndexed[Labeled[WireworldPlot[#1,PlotRange->{{3,11},{15,30}}],#2[[1]]-1,LabelStyle->Large]&]@
WireworldEvolve[Wireworld[],23],
SaveDefinitions->True,
AnimationRate->1.5
]

A great explanation of how this works can be found in https://www.quinapalus.com/wires5.html (this page also links to many other alternatives)

The ROM

We can create a read-only memory (ROM) by arranging multiple AND-NOT gates in a grid pattern

Each gate stores one bit of data:

GraphicsRow[{Labeled[WireworldPlot[SparseArray[]],"Stores binary 0"],Labeled[WireworldPlot[SparseArray[]],"Stores binary 1"]}]

‘0’ is stored by adding an “ear” (extra copper cell) to the gate’s northeast output

‘1’ is stored by leaving this ear out

The ROM

How is the data read from the ROM?

An electron enters from the bottom of the ROM array

It travels northwest through the grid of gates

At each gate missing an ear (storing ‘1’), a new electron is emitted from the northeast output

These emitted electrons flow northeast and exit the ROM’s edge

ListAnimate[
GraphicsRow/@Map[WireworldPlot[#,ImageSize->Small]&,Transpose@Map[WireworldEvolve[#,13]&,{SparseArray[],SparseArray[]}],{2}],
SaveDefinitions->True,
AnimationRate->1.5
]

A minimal decoder

Below is a ROM design that functions as a 2-bit memory with address decoding

It’s capable of storing all possible 2-bit binary numbers (00, 01, 10, 11)

Row[{WireworldPlot[SparseArray[],Mesh->None,PlotRange->{{8,39},{4,26}}],Column[{,},Spacings->5]}]

A minimal decoder

For the number 1, only the right wire lights up with an electron (binary 01):

ListAnimate[
MapIndexed[Labeled[Rotate[WireworldPlot[#1,Mesh->None],-90Degree],"Address signal #1, step "<>StringPadLeft[ToString[#2[[1]]-1],2],Top,LabelStyle->Medium]&]@
WireworldEvolve[SparseArray[],43],
SaveDefinitions->True]

For the number 2, only the left wire activates (binary 10):

ListAnimate[
MapIndexed[Labeled[Rotate[WireworldPlot[#1,Mesh->None],-90Degree],"Address signal 2, step "<>StringPadLeft[ToString[#2[[1]]-1],2],Top,LabelStyle->Medium]&]@
WireworldEvolve[SparseArray[],48],
SaveDefinitions->True]

For the number 3, both wires light up at the same time (binary 11):

ListAnimate[
MapIndexed[Labeled[Rotate[WireworldPlot[#1,Mesh->None],-90Degree],"Address Signal 3, step "<>StringPadLeft[ToString[#2[[1]]-1],2],Top,LabelStyle->Medium]&]@
WireworldEvolve[SparseArray[],53],
SaveDefinitions->True]

This shows how the ROM uses electron pulses to represent numbers — one wire for each digit in the binary code

A look-up table for seven-segment displays

DynamicModule[{rom=SparseArray[],romMod,setAddressLine,d,x},
setAddressLine[{x_,y_}]:=(
romMod=rom;
romMod[[x,y]]=1;
romMod[[x+1,y]]=2;
);
d=5;
setAddressLine[n_Integer]:=setAddressLine[{94-d*n,38+5*n}];
x=Table[
setAddressLine[addr-1];
ListAnimate[WireworldPlot[#,Mesh->None]&/@WireworldEvolve[romMod,120],SaveDefinitions->True],
{addr,Range[1,10]}
];
Manipulate[
x[[address]]
,
{address,Range[1,10]}
]
]

ROM shown above has ten inputs and seven outputs

Used in the computer as look-up tables to drive seven-segment displays

The flip-flop

A device that can be in one of two states: set or reset

The flip-flop shown here has two inputs: the lower one sets the flip-flop, the upper one resets it

ListAnimate[
MapIndexed[
Labeled[WireworldPlot[#1,Mesh->None,PlotRange->{{3,13},{16,48}},ImageSize->Medium],#2[[1]]-1]&,
WireworldEvolve[SparseArray[],29]
],
SaveDefinitions->True,
AnimationRate->2
]

Flip-flop state held in a 6-cell loop

Electron circulates continuously while set

Bottom of loop → OR gate with set signal

Top of loop → AND-NOT gate for clear signal
	Timed pulse removes electron to clear flip-flop

The binary adder

An adder for binary numbers encoded serially (least-significant bit first):

wireworldBinaryAdd[3,6]

The binary adder: encoding and decoding

ClearAll[wireworldBinaryAdd]
wireworldBinaryAdd[x_Integer,y_Integer,mag_:0.5]/;(0<=x<=7&&0<=y<=7):=
Module[{binaryAdder,xbin,ybin,evolve,rbin},
binaryAdder=SparseArray[];
(* Encoding x *)
xbin=IntegerDigits[x,2,3];(* e.g. 3 is '011' *)
(* Encoding 1st bit *)If[xbin[[1]]==1,binaryAdder[[6,4]]=1;binaryAdder[[6,3]]=2;];
(* Encoding 2nd bit *)If[xbin[[2]]==1,binaryAdder[[6,10]]=1;binaryAdder[[6,9]]=2;];
(* Encoding 3rd bit *)If[xbin[[3]]==1,binaryAdder[[6,16]]=1;binaryAdder[[6,15]]=2;];

(* Encoding y *)
ybin=IntegerDigits[y,2,3];(* e.g. 6 is '110' *)
(* Encoding 1st bit *)If[ybin[[1]]==1,binaryAdder[[12,4]]=1;binaryAdder[[12,3]]=2;];
(* Encoding 2nd bit *)If[ybin[[2]]==1,binaryAdder[[12,10]]=1;binaryAdder[[12,9]]=2;];
(* Encoding 3rd bit *)If[ybin[[3]]==1,binaryAdder[[12,16]]=1;binaryAdder[[12,15]]=2;];

(* Run 48 generations *)
evolve=WireworldEvolve[binaryAdder,48];

(* Print animation *)
Print[Magnify[ListAnimate[MapIndexed[WireworldPlot[#,]&,evolve],],mag]];

(* Extract cell values from last state *)
rbin=Extract[evolve[[-1]],{{7,45},{7,51},{7,57},{7,63}}];
(* Convert 1's to 1 and others to 0 *)
rbin=Boole[Map[#===1&,rbin]];
(* Decode binary result *)
FromDigits[rbin,2]
]

The binary adder: examples

wireworldBinaryAdd[1,0]

wireworldBinaryAdd[1,1]

wireworldBinaryAdd[1,2]

wireworldBinaryAdd[1,3]

wireworldBinaryAdd[7,7]

The binary added: how it works

wireworldBinaryAdd[3,6]

1st gate: a XOR gate and computes A ⊻ B

This represents the sum of the first bits

2nd gate (just below): is a simple AND-NOT gate and computes A ∧ (¬A ⊻ B) ⟶ A ∧ B

This represents the carry of the first bits

BooleanConvert[A ∧ (¬(A ⊻ B))]

This represents the half-adder

ListAnimate[MapIndexed[WireworldPlot[#,Mesh->None,ImageSize->Medium,Epilog->{Style[Text[StringPadLeft[ToString[#2[[1]]-1],2," "],{-1,10}],Medium],Style[Text["\!\(\*SubscriptBox[\(B\), \(2\)]\)",{3.5`,18}],Medium,White],Style[Text["\!\(\*SubscriptBox[\(B\), \(1\)]\)",{9.5`,18}],Medium,White],Style[Text["\!\(\*SubscriptBox[\(B\), \(0\)]\)",{15.5`,18}],Medium,White],Style[Text["\!\(\*SubscriptBox[\(A\), \(2\)]\)",{3.5`,5}],Medium,White],Style[Text["\!\(\*SubscriptBox[\(A\), \(1\)]\)",{9.5`,5}],Medium,White],Style[Text["\!\(\*SubscriptBox[\(A\), \(0\)]\)",{15.5`,5}],Medium,White],Style[Text["\!\(\*SubscriptBox[\(R\), \(3\)]\)",{44.5`,17}],Medium,White],Style[Text["\!\(\*SubscriptBox[\(R\), \(2\)]\)",{50.5`,17}],Medium,White],Style[Text["\!\(\*SubscriptBox[\(R\), \(1\)]\)",{56.5`,17}],Medium,White],Style[Text["\!\(\*SubscriptBox[\(R\), \(0\)]\)",{62.5`,17}],Medium,White]},ImagePadding->35,Frame->False]&,WireworldEvolve[Wireworld[],31]],SaveDefinitions->True,AnimationRunning->False,AnimationRepetitions->1]

The binary added: how it works

wireworldBinaryAdd[3,6]

2nd AND-NOT gate: inputs are A ⊻ B and constant 1s (given by the loop in the north east) ⟶ ¬(A ⊻ B)

Simplify[True&&(¬(A⊻B))]

This signal is used to clear the carry flip-flop (bottom)

The binary added: how it works

wireworldBinaryAdd[3,6]

wireworldBinaryAdd[2^^101,2^^110]

TextGrid[{
{"A","B","Set == A ∧ B","Clear == ¬(A ⊻ B)","Effect"},
{0,0,0,1,"Clear"},
{0,1,0,0,"Unchanged"},
{1,0,0,0,"Unchanged"},
{1,1,1,1,"Set"}
},
Dividers->{{2->DarkRed,-2->DarkRed,3->Dashed,4->Dashed},{2->DarkRed,3->Dashed,4->Dashed,5->Dashed}},
Alignment->Center
]

If both A and B are 0, there is no carry from this bit into the next

If exactly one of A and B is 1, the carry out of this bit is the same as the carry in to it

If both A and B are 1, there is definitely a carry out of this bit

The binary added: how it works

wireworldBinaryAdd[3,6]

The final step is to XOR the carry state C (trapped in the flip-flop) with A ⊻ B (coming from the top) to give one bit of the sum

TextGrid[{
{"A","B","Carry (before)","Sum","Carry (after)"},
{0,0,0,0,"cleared (0->0)"},
{0,0,1,1,"cleared (1->0)"},
{0,1,0,1,"unchanged (0->0)"},
{0,1,1,0,"unchanged (1->1)"},
{1,0,0,1,"unchanged (0->0)"},
{1,0,1,0,"unchanged (1->1)"},
{1,1,0,0,"set (0->1)"},
{1,1,1,1,"set (1->1)"}
},
Dividers->{{2->Dashed,-3->DarkRed,3->Dashed,5->Dashed},{2->DarkRed,3->Dashed,4->Dashed,5->Dashed,6->Dashed,7->Dashed,8->Dashed,9->Dashed}},
Alignment->Center
]

The clock system

The simplest way to generate a period P signal is to use a loop with size P containing a single electron:

ListAnimate[WireworldPlot[#,Mesh->None,ImageSize->Medium]&/@WireworldEvolve[SparseArray[],11],DefaultDuration->2,SaveDefinitions->True]

This approach works well for small values of P but become space expensive with bigger values:

ListAnimate[WireworldPlot[#,Mesh->None,ImageSize->Medium]&/@WireworldEvolve[SparseArray[],23],DefaultDuration->4,SaveDefinitions->True]

The clock system

Clock injector

Frequency divider

2 logic gates (XOR and AND-NOT)

1 loop, which stores an electron and acts like a flip-flop

1 delay path, which introduces a fixed timing difference between signals

Clock injector

Simple loop injects an electron into the clock divider chain every 36 steps:

ListAnimate[MapIndexed[WireworldPlot[#1,Mesh->False,ImageSize->Medium,Frame->None,PlotRange->{{4,8},{1,21}}]&,WireworldEvolve[SparseArray[],36]]]

This acts as the main clock signal

The frequency divider

Halves the frequency of the clock signal — outputs one pulse for every two input pulses

ListAnimate[MapIndexed[Labeled[WireworldPlot[#,Mesh->True,ImageSize->Medium,Frame->None],Style[StringPadLeft[ToString[#2[[1]]-1],3," "],Large],Right]&,WireworldEvolve[SparseArray[],144]],AnimationRate->2]

89-17

Loop (electron generator)

A closed 12-cell path that continually circulates an electron once it’s full

Can be either empty (off) or filled (on)

When full, it produces electrons timed to meet the injector’s electrons at the XOR gate

ListAnimate[Map[WireworldPlot[#,Mesh->True,ImageSize->Small,Frame->None,PlotRange->{{4,13},{21,27}}]&,WireworldEvolve[SparseArray[],88]]]

Delay path

Delays the arrival of electrons by a specific number of generations

4 generations to reach the A input of the A ∧ ¬B gate

16 generations to reach the B input

ListAnimate[Map[WireworldPlot[#,Mesh->True,ImageSize->Small,Frame->None,PlotRange->{{4,17},{25,44}}]&,WireworldEvolve[SparseArray[],88]]]

A 12-generation delay between the two inputs

Logic gates

XOR gate: combines signals from the loop and the clock injector

A ∧ ¬B: produces an output only when A = 1 and B = 0

“blocks” electrons when both signals are active

ListAnimate[Map[WireworldPlot[#,Mesh->True,ImageSize->Small,Frame->None,PlotRange->{{4,17},{18,44}}]&,WireworldEvolve[SparseArray[],88]]]

The frequency divider

TextGrid[{
{"Input clock oulse","Loop state (before)","Loop state (after)","Output"},
{"1st pulse","empty","filled","✓ Passes"},
{"2nd pulse","filled","emptied","\[Cross] Blocked"},
{"3rd pulse","empty","filled","✓ Passes"},
{"4th pulse","filled","emptied","\[Cross] Blocked"}
},
Dividers->{{2->Dashed,3->Dashed,4->Dashed},{2->DarkRed,3->Dashed,4->Dashed,5->Dashed}}
]

ListAnimate[MapIndexed[Labeled[WireworldPlot[#,Mesh->None,ImageSize->Small,Frame->None],Style[StringPadLeft[ToString[#2[[1]]-1],3," "],Medium],Right]&,WireworldEvolve[SparseArray[],144]]]

The clock system

2 frequency dividers ⟶ T == 144 (signal at generations 32 and 176):

ListAnimate[MapIndexed[
Labeled[
WireworldPlot[#,Mesh->None,ImageSize->Medium,Frame->None,PlotRange->{Automatic,{1,41}}],
Style[StringPadLeft[ToString[#2[[1]]-1],3," "]]
]&,WireworldEvolve[SparseArray[],176]],AnimationRunning->False,
AnimationRepetitions->1
]

3 frequency dividers ⟶ T == 288 (signal at generations 48 and 336):

ListAnimate[MapIndexed[
Labeled[
WireworldPlot[#,Mesh->None,ImageSize->Medium,Frame->None,PlotRange->{Automatic,{1,51}}],
Style[StringPadLeft[ToString[#2[[1]]-1],3," "]]
]&,WireworldEvolve[SparseArray[],336]],AnimationRunning->False,
AnimationRepetitions->1
]

The clock system

The Wireworld computer has outputs after 5h and 6h division unit:

ListAnimate[MapIndexed[
Labeled[
WireworldPlot[#,Mesh->None,ImageSize->Medium,Frame->None,PlotRange->{Automatic,{1,81}}],
Style[StringPadLeft[ToString[#2[[1]]-1],3," "]]
]&,WireworldEvolve[SparseArray[],336]],AnimationRunning->False,
AnimationRepetitions->1
]

Frequencies:

{36*2^5,36*2^6}

Generations with signals:

Threaded[{5*32,6*32}]+Table[{36*2^5*i,36*2^6*i},{i,0,10}]

The digit display

A digit display is composed of:

7 segments display

ROM with 10 inputs and 7 outputs

ROM controller

The 7 segments display

The 7-segment display us arranged so each segment corresponds to a separate wire path

WireworldPlot[Wireworld[],ImageSize->Small]

Each segment is controlled by an input wire that distributes signals to the corresponding section of the display

Electrons moving through a wire represent a lit segment

ListAnimate[WireworldPlot[#,ImageSize->Medium]&/@WireworldEvolve[Wireworld[],120]]

To make segments appear brighter, electrons must be close together — achieved using 3-micron technology

ListAnimate[WireworldPlot[#,ImageSize->Medium]&/@WireworldEvolve[Wireworld[],120]]

The rest of the circuit uses 6-micron technology, so a frequency doubler is added at each input

DynamicModule[{state2=Wireworld[]},
Dynamic[
WireworldPlot[state2=WireworldEvolve[state2,{{1}}],ImageSize->Small,PlotRange->{{1,106},{25,90}},Mesh->None],
TrackedSymbols:>{},
UpdateInterval->0.25
]
]

The ROM

It encodes the correspondence between a digit and its representation on the 7 segments display

DynamicModule[{state2=Wireworld[]},
Dynamic[
WireworldPlot[state2=WireworldEvolve[state2,{{1}}],ImageSize->Medium,PlotRange->{{98,180},{1,90}},Mesh->None],
TrackedSymbols:>{},
UpdateInterval->0.1
]
]

To maintain segments lighted the ROM input corresponding to the digit must be continuously supplied

This feature is implemented by ROM controller

The ROM controller

The ROM controller manages which ROM input is currently active

Each ROM input is a loop with a period of 6 generations

An OR gate → to inject a new electron into the loop

An AND-NOT gate → to empty the loop when needed

DynamicModule[{st=Wireworld[]},
Dynamic[
WireworldPlot[st=WireworldEvolve[st,{{1}}],ImageSize->Small,Mesh->None],
TrackedSymbols:>{},
UpdateInterval->.1
]
]

The ROM controller

Loops are connected in sequence so that only one loop is active at a time

Each loop can pass its electron to the next loop when the right time comes

Each loop is connected to the next through two signals, called A and B

The A input tells the next loop when it’s allowed to receive an electron

The B input tells the previous loop when it’s time to clear itself

When the controller decides to move forward, the current loop’s electron travels to the next loop

That new loop becomes the active one, and the previous loop gets emptied automatically

DynamicModule[{st=Wireworld[]},
Dynamic[
WireworldPlot[st=WireworldEvolve[st,{{1}}],ImageSize->Small,Mesh->None],
TrackedSymbols:>{},
UpdateInterval->.5
]
]

The digit display

DynamicModule[{state0=Wireworld[],state1=Wireworld[],state2=Wireworld[],state3=Wireworld[]},
Dynamic@Row[{
WireworldPlot[state0=WireworldEvolve[state0,{{1}}],ImageSize->Small],
WireworldPlot[state1=WireworldEvolve[state1,{{1}}],ImageSize->Small],
WireworldPlot[state2=WireworldEvolve[state2,{{1}}],ImageSize->Small],
WireworldPlot[state3=WireworldEvolve[state3,{{1}}],ImageSize->Small]
}]
]

The register bank

At the center of the Wireworld computer is the register bank — a small memory system made up of 64 registers, and each one can hold a 16-bit number

DynamicModule[{st=Wireworld[]},
Dynamic[
WireworldPlot[st=WireworldEvolve[st,{{1}}],ImageSize->Medium,Mesh->None],
TrackedSymbols:>{},UpdateInterval->.1
]
]

Each register is built as a loop of cells, just big enough to hold those 16 bits

The bits (electrons) are always moving in circles inside their loops
⟶ That constant motion keeps the data “alive”

Reading data

To read from a register, the system sends two sequences of electrical pulses — one going up an outer wire, and a moment later another going up an inner wire

These two pulses eventually collide somewhere in the bank

When they collide, special logic detects that collision and triggers one specific register — the one we want to read

Once that register is triggered, its 16-bit data start moving out through a chain of logic gates on the left side

time skip...

The prime computer

Design was done by David Moore and Mark Owen, with the help of many others, between 1990 and 1992

primeComputer=Wireworld[];

WireworldPlot[primeComputer,ImageSize->500,Frame->None]

DynamicModule[{state=primeComputer,gen=0,inc=72},
Dynamic[
gen+=inc;
Panel[WireworldPlot[state=WireworldEvolve[state,{{inc}},Method->"Library"],ImageSize->Medium,Mesh->None,Frame->None],Style[Row[{"Generation: ",gen}],Medium]]
]
]

Bonus: The binary multiplier

Nick Gardner showed in 2002 how two 8-bit binary numbers could be multiplied

binaryMultiplier=Wireworld[];

DynamicModule[{state=binaryMultiplier},
Dynamic[
WireworldPlot[state=WireworldEvolve[state,{{1}}],ImageSize->Medium]
]
]

https://mathworld.wolfram.com/WireWorld.html

Resources

https://www.quinapalus.com/wi-index.html

https://resources.wolframcloud.com/PacletRepository/resources/DanielS/Wireworld/

https://wiki.logre.eu/index.php/Projet_Wireworld/en

https://mathworld.wolfram.com/WireWorld.html

Lighting up WireWorld by image processing and exploring complex circuitry

Wireworld: Turing-complete cellular automaton for simulating logic gates

Contact

daniels@wolfram.com

https://community.wolfram.com/web/daniels