Register File & Address Latch
The register file was mainly developed by Julius Herrmann, Robert Pietsch and Julian Rederlechner. It is in working condition and has been integrated into the final build.
Overview
Our processor has a register file consisting of 16 8-bit registers. For certain operations (e.g. memory addressing), one can combine pairs of neighboring 8-bit HI and LO registers to form 16-bit registers as can be seen in the table below.
Upper 8 bits | Lower 8 bits | Purpose |
---|---|---|
a | b | General-Purpose (combined 16-bit reg) |
c | d | General-Purpose (combined 16-bit reg) |
Page | Index | General-Purpose; Indirect memory accessing |
Stack pointer | Stack pointer | Points towards the lowest stack content |
Program counter | Program counter | Points towards the next instruction |
u | v | Hidden register (used for calls etc.) |
w | x | Hidden register (during interrupts: saved PC) |
y | z | Hidden register (during interrupts: y ACCU, z FLAGS) |
Apart from these registers, there also are the 4-bit wide FLAGS
and the 8-bit wide ACCU
register that are part of the ALU.
This module also contains the address latch, which is placed right next to the register file: It is a 16-bit buffer with the added functionality of incrementing and decrementing. The address latch is transparent to the ISA. Its stored value is permanently emitted to the memory unit and to the I/O devices.
Schematics and Chip Layout
The register file is split into two parts, namely the control line decoder and the actual register unit.
Register Unit (Schematic)
Control Line Decoder (Schematic)
Inner Workings
Control Line | Type | Purpose |
---|---|---|
REG_R_IDX[0:2] | Active High | Selects the 16-bit register to read from. |
REG_W_IDX[0:2] | Active High | Selects the 16-bit register to write to. |
REG_W_SEL_LO , REG_W_SEL_HI | Active High | Encodes which register half(s) to write to. The encoding is further specified below. |
~REG_LATCH_LOAD | Active Low | Loads the value from the 16-bit register selected by REG_R_IDX[0:2] into the address latch. |
~REG_LATCH_COUNT | Active Low | In-/Decrements the address latch contents depending on the state of REG_LATCH_UP/~DOWN . |
REG_LATCH_UP/~DOWN | Active High | Encodes incrementing (high) or decrementing (low) for REG_LATCH_COUNT signals. |
~REG_LO_TO_DBUS | Active Low | Emits the LO part of the 16-bit register selected by REG_R_IDX[0:2] to the data bus. |
~REG_HI_TO_DBUS | Active Low | Emits the HI part of the 16-bit register selected by REG_R_IDX[0:2] to the data bus. |
Additionally, we have some register-internal control lines coming from the register decode logic. These are denoted in italics font style within the schematics.
Encoding of REG_W_SEL_LO
and REG_W_SEL_HI
L, L
: No register is written to.L, H
: The data bus value is written to the HI part of the register selected byREG_W_IDX
.H, L
: The data bus value is written to the LO part of the register selected byREG_W_IDX
.H, H
: The address bus value is written into the register selected byREG_W_IDX
.
Register File
We split the register file into two halves (HI and LO) and each half into a register block denoted by a Greek letter (, , and ).
This allows us to map each block to a pair of 74-670 quad four-bit registers (RegAlpha0
, RegAlpha1
, RegBeta0
, ..., RegDelta1
).
On a micro-architectural level, each register is identified by a combination of REG_R_IDX[0:2]
and ~REG_LO_DBUS
/~REG_HI_DBUS
(for reading), or REG_W_IDX[0:2]
and REG_W_SEL_LO
/REG_W_SEL_HI
(for writing).
The lower two bits of REG_R_IDX
and REG_W_IDX
are used to address the correct four bits within each register chip while REG_R_IDX[2]
and REG_W_IDX[2]
are used to address the correct register pair within the HI and LO halves.
We chose this more complex addressing approach over using 74-377 eight-bit registers as the higher data density of 16 bits per chip lets us save breadboard space.
A disadvantage of our decision is that it is not possible to permanently output register contents for debugging.
The following graphic visualizes the register addressing.
Writing into registers
At rising clock edges, register can be filled with values coming from either the data bus or from the address latch.
The selection is implemented using two pairs of 74-245 bus transceivers (HiDataIn
and LoDataIn
for the data bus, as well as HiAddrIn
and LoAddrIn
for the address latch).
The correct transceiver pair is selected from the combination of REG_W_SEL_LO
and REG_W_SEL_HI
which gets decoded into the internal control lines ~REG_R_DATA
and ~REG_R_ADDR
within the control decoder.
Because writing on the 74-670 register chips used is level-triggered, we built a small edge-detector within the control decoder that additionally delays the write-enable signals (internal control lines ~REG_W_ALPHA
, ..., ~REG_W_DELTA
).
This allows the control lines to reach a stable state before the actual write happens such that no other registers get overwritten.
Emitting register contents
Register values can be emitted to either the data bus or for storing in the address latch.
In both cases, both the HI and LO register blocks addressed by REG_R_IDX[2]
are enabled to output their contents.
If the data is loaded into the address latch, outputs from both halves are used.
For data bus output, ~REG_HI_TO_DBUS
and ~REG_LO_TO_DBUS
are used to select the register block that is emitted onto the data bus.
Address Latch
The address latch is implemented as a set of four 74-191 presettable four-bit counters.
It can either be filled with a value from a register pair using ~REG_LATCH_LOAD
, or it can be in-/decremented using ~REG_LATCH_COUNT
where the direction can be selected using REG_LATCH_UP/~DOWN
.
Control Line Decoder
The control line decoder (CLD) has two major purposes:
- It contains an edge-detector with additional delay that allows to load data into registers and the address latch after control lines have stabilized.
- It decodes the incoming control lines used for register addressing into control lines for each physical register chip. This is done to reduce the number of control lines needed as there are plenty of unused combinations of register input signals.
Tips for Reproduction
- We initially had major problems with data bleeding over into registers that weren't actually selected on writes. This was due to control lines not having settled into their final states when the register chips' write inputs were enabled. A fix for this problem is to minimally delay the write signal. We implemented this as part of the edge-detector circuit.
- Our design of a register file is modular allowing for testing each register block individually before connecting them to form the register file. We initially skipped testing the individual blocks leading to us having to deconstruct the register file, test and fix each block before reconstructing the whole register file. Therefore, we recommend taking the time to already test early during construction.
- If you've got more breadboard space and chips available than with our layouts, we recommend building individual registers from 8-bit register chips (e.g. 74-377) and bus transceivers. This allows to permanently display the register contents which makes debugging significantly easier.
- Bit orders tend to get messy quickly. Always make sure to write down where the most and the least significant bits are stored and think about all places where the order matters (e.g. the order of the address latch counter chips, especially the direction of the carry signals).
- The register file consists of hundreds of cables which means that some misplaced or missing cables are to be expected. We combatted this problem by drawing chip layout diagrams and giving each group of directly-connected pins a distinct number. Afterwards, we checked each pin to have correct connections to all other pins with matching numbers. Additionally, we recommend printing out such a labeled diagram such that already-checked networks can be crossed out.
- Moreover, using the technique of drawing the chip layout in advance, we tried to find the optimal arrangement of chips on the boards. Pretty early on, we realized that finding the perfect layout was nearly impossible, and that we would have to find other ways to clean up our wiring, and more importantly, make our project maintainable. To solve this problem, we decided to extend our wiring into the third dimension. By introducing three different height levels, we were able to put wires that don't necessarily need to be accessed, such as 5V and ground, on the lowest level, anything related to data flow that might require fixes on the middle level, and all the control lines that frequently have to be disconnected and reconnected during troubleshooting on the top.
Testing
For testing, we used an Arduino Mega microcontroller that emulated the control lines and data bus. We wrote a fuzzer that filled registers in all possible orders with random data and afterwards checked the registers to hold the expected values. During the problem of values bleeding into unselected registers, we expanded the fuzzer to use values following fixed patterns such that we could trace data bleeds. We wrote multiple variants of the fuzzer for testing single register blocks, register file halves and later on for the complete register file including the address latch.
To test the control line decoder, we hooked up the Arduino Mega to both the in- and outputs of the circuit and simulated the required behavior in software. Using this technique allowed us to test every possible combination of input on both implementations of the logic and compare the two outcomes.