Project 3 · RAM
RAM: a bank of registers plus address decoding
RAM introduces no new part. It just puts many registers side by side and uses the address to decide which one is written and which one is read. To grow, stack the previous level again.
01One address, two paths
A write uses DMux to send load to one register; a read uses Mux to select that register’s contents. The address drives both selectors, so what goes in and what comes out are always the same slot.
Note: with load=0 all eight DMux outputs are 0 and no register is written — that is what “read without writing” means.
02How five levels of capacity stack up
Each level does one thing: place eight copies of the level below side by side, and split the address into “high bits pick the block, low bits pick the word”.
| Chip | Built from | Address bits | Capacity |
|---|---|---|---|
| RAM8 | 8 × Register | 3 | eight 16-bit words |
| RAM64 | 8 × RAM8 | 6 | 64 words |
| RAM512 | 8 × RAM64 | 9 | 512 words |
| RAM4K | 8 × RAM512 | 12 | 4096 words |
| RAM16K | 4 × RAM4K | 14 | 16384 words |
The one exception is RAM16K: 16K ÷ 4K = 4, so it is four RAM4K blocks, using DMux4Way and Mux4Way16.
03RAM8: eight registers and two 8-way selectors
The simplest RAM: 3 address bits, exactly enough to pick one of eight registers.
Click an address to pick a register, change in, then press “Write once” — the value lands only when load=1.
CHIP RAM8 { IN in[16], load, address[3]; OUT out[16]; PARTS: DMux8Way(in=load, sel=address, a=l0, b=l1, c=l2, d=l3, e=l4, f=l5, g=l6, h=l7); Register(in=in, load=l0, out=r0); Register(in=in, load=l1, out=r1); Register(in=in, load=l2, out=r2); Register(in=in, load=l3, out=r3); Register(in=in, load=l4, out=r4); Register(in=in, load=l5, out=r5); Register(in=in, load=l6, out=r6); Register(in=in, load=l7, out=r7); Mux8Way16(a=r0, b=r1, c=r2, d=r3, e=r4, f=r5, g=r6, h=r7, sel=address, out=out); }
Eight Register chips with one DMux8Way (write) and one Mux8Way16 (read). The same address decides which one is written and which one is read.
04RAM64: eight RAM8 blocks
The 6-bit address splits: address[3..5] picks the RAM8 block and address[0..2] picks the register inside it.
CHIP RAM64 { IN in[16], load, address[6]; OUT out[16]; PARTS: DMux8Way(in=load, sel=address[3..5], a=l0, b=l1, c=l2, d=l3, e=l4, f=l5, g=l6, h=l7); RAM8(in=in, load=l0, address=address[0..2], out=r0); RAM8(in=in, load=l1, address=address[0..2], out=r1); RAM8(in=in, load=l2, address=address[0..2], out=r2); RAM8(in=in, load=l3, address=address[0..2], out=r3); RAM8(in=in, load=l4, address=address[0..2], out=r4); RAM8(in=in, load=l5, address=address[0..2], out=r5); RAM8(in=in, load=l6, address=address[0..2], out=r6); RAM8(in=in, load=l7, address=address[0..2], out=r7); Mux8Way16(a=r0, b=r1, c=r2, d=r3, e=r4, f=r5, g=r6, h=r7, sel=address[3..5], out=out); }
Eight RAM8 blocks: address[3..5] picks the block, address[0..2] picks the register inside it.
05RAM512: eight RAM64 blocks
The same trick once more: the top 3 bits pick the block, the low 6 pass straight through to RAM64.
CHIP RAM512 { IN in[16], load, address[9]; OUT out[16]; PARTS: DMux8Way(in=load, sel=address[6..8], a=l0, b=l1, c=l2, d=l3, e=l4, f=l5, g=l6, h=l7); RAM64(in=in, load=l0, address=address[0..5], out=r0); RAM64(in=in, load=l1, address=address[0..5], out=r1); RAM64(in=in, load=l2, address=address[0..5], out=r2); RAM64(in=in, load=l3, address=address[0..5], out=r3); RAM64(in=in, load=l4, address=address[0..5], out=r4); RAM64(in=in, load=l5, address=address[0..5], out=r5); RAM64(in=in, load=l6, address=address[0..5], out=r6); RAM64(in=in, load=l7, address=address[0..5], out=r7); Mux8Way16(a=r0, b=r1, c=r2, d=r3, e=r4, f=r5, g=r6, h=r7, sel=address[6..8], out=out); }
Eight RAM64 blocks: the top 3 bits pick the block, the low 6 are the address inside it.
06RAM4K: eight RAM512 blocks
A 12-bit address: the top 3 bits pick the block, the low 9 address inside it.
CHIP RAM4K { IN in[16], load, address[12]; OUT out[16]; PARTS: DMux8Way(in=load, sel=address[9..11], a=l0, b=l1, c=l2, d=l3, e=l4, f=l5, g=l6, h=l7); RAM512(in=in, load=l0, address=address[0..8], out=r0); RAM512(in=in, load=l1, address=address[0..8], out=r1); RAM512(in=in, load=l2, address=address[0..8], out=r2); RAM512(in=in, load=l3, address=address[0..8], out=r3); RAM512(in=in, load=l4, address=address[0..8], out=r4); RAM512(in=in, load=l5, address=address[0..8], out=r5); RAM512(in=in, load=l6, address=address[0..8], out=r6); RAM512(in=in, load=l7, address=address[0..8], out=r7); Mux8Way16(a=r0, b=r1, c=r2, d=r3, e=r4, f=r5, g=r6, h=r7, sel=address[9..11], out=out); }
Eight RAM512 blocks: the top 3 bits pick the block, the low 9 go inside.
07RAM16K: four RAM4K blocks
The last level differs: 16K is only four 4K blocks, so it uses 4-way selectors. Of the 14 address bits, the top 2 pick the block.
CHIP RAM16K { IN in[16], load, address[14]; OUT out[16]; PARTS: DMux4Way(in=load, sel=address[12..13], a=l0, b=l1, c=l2, d=l3); RAM4K(in=in, load=l0, address=address[0..11], out=r0); RAM4K(in=in, load=l1, address=address[0..11], out=r1); RAM4K(in=in, load=l2, address=address[0..11], out=r2); RAM4K(in=in, load=l3, address=address[0..11], out=r3); Mux4Way16(a=r0, b=r1, c=r2, d=r3, sel=address[12..13], out=out); }
Four RAM4K blocks: 16K ÷ 4K = 4, so this level uses DMux4Way and Mux4Way16.
08How the address is sliced
Line up the slicing at every level and the rule is clear: high bits select, low bits pass through.
| Chip | High bits (block) | Low bits (inside) | Meaning |
|---|---|---|---|
| RAM64 | address[3..5] | address[0..2] | top 3 bits pick the RAM8 block |
| RAM512 | address[6..8] | address[0..5] | top 3 bits pick the RAM64 block |
| RAM4K | address[9..11] | address[0..8] | top 3 bits pick the RAM512 block |
| RAM16K | address[12..13] | address[0..11] | top 2 bits pick the RAM4K block |
That is also why RAM16K takes a 14-bit address: 214 = 16384.