16-bit Register
A register stores data and outputs it on demand. This example shows a 16-bit register with load enable.
Design Overview
A register is built from D flip-flops. In SHDL, we typically use a simplified model where:
- When
Loadis high, the register captures the input - The stored value is always available on the output
Single-Bit Register Cell
First, let's build a single-bit memory cell:
component BitCell(D, Load) -> (Q) {
# Multiplexer: select between old value and new input
muxAnd1: AND; # D & Load
muxAnd2: AND; # Q & ~Load
muxOr: OR;
notLoad: NOT;
# Feedback loop (this is a simplified latch model)
# In real hardware, this would need proper clocking
connect {
# Invert Load for the "keep old value" path
Load -> notLoad.A;
# If Load=1, pass D through
D -> muxAnd1.A;
Load -> muxAnd1.B;
# If Load=0, keep current Q (feedback)
# Note: In simulation, this creates a feedback loop
# that the simulator handles through iterative settling
muxOr.O -> muxAnd2.A;
notLoad.O -> muxAnd2.B;
# Combine paths
muxAnd1.O -> muxOr.A;
muxAnd2.O -> muxOr.B;
# Output
muxOr.O -> Q;
}
}
16-bit Register
Now we compose 16 bit cells into a full register:
use bitcell::{BitCell};
component Reg16(D[16], Load) -> (Q[16]) {
# 16 bit cells, one for each bit
>i[16]{
cell{i}: BitCell;
}
connect {
# Connect each bit cell
>i[16]{
D[{i}] -> cell{i}.D;
Load -> cell{i}.Load;
cell{i}.Q -> Q[{i}];
}
}
}
How It Works
-
When
Load = 1:muxAnd1passes the inputDthroughmuxAnd2is blocked (itsLoadinput is 0 after NOT)- The new value propagates to
Q
-
When
Load = 0:muxAnd1is blockedmuxAnd2passes the currentQback around- The value is retained
Alternative: Using a 2-to-1 Mux
A cleaner implementation uses a dedicated multiplexer:
use mux2::{Mux2};
component BitCellMux(D, Load) -> (Q) {
mux: Mux2;
connect {
# Sel=0: keep current value (feedback from Q)
# Sel=1: load new value from D
mux.O -> mux.A; # Feedback path
D -> mux.B;
Load -> mux.Sel;
mux.O -> Q;
}
}
Where Mux2 is:
component Mux2(A, B, Sel) -> (O) {
# O = (A AND NOT Sel) OR (B AND Sel)
notSel: NOT;
andA: AND;
andB: AND;
orOut: OR;
connect {
Sel -> notSel.A;
A -> andA.A;
notSel.O -> andA.B;
B -> andB.A;
Sel -> andB.B;
andA.O -> orOut.A;
andB.O -> orOut.B;
orOut.O -> O;
}
}
Register File
A collection of registers with addressing:
use reg16::{Reg16};
use decoder::{Decoder2to4};
use mux4_16::{Mux4_16};
component RegFile4x16(
WriteData[16],
WriteAddr[2],
WriteEn,
ReadAddr[2]
) -> (ReadData[16]) {
# 4 registers
>i[4]{
reg{i}: Reg16;
}
# Write address decoder
writeDec: Decoder2to4;
# AND each decoder output with WriteEn
>i[4]{
loadAnd{i}: AND;
}
# Read multiplexer (4-to-1, 16-bit wide)
readMux: Mux4_16;
connect {
# Decode write address
WriteAddr[:2] -> writeDec.A[:2];
# Generate load signals: decoder output AND WriteEn
>i[4]{
writeDec.Y[{i}] -> loadAnd{i}.A;
WriteEn -> loadAnd{i}.B;
loadAnd{i}.O -> reg{i}.Load;
}
# Connect write data to all registers
>i[4]{
>j[16]{
WriteData[{j}] -> reg{i}.D[{j}];
}
}
# Read multiplexer inputs
>j[16]{
reg1.Q[{j}] -> readMux.A[{j}];
reg2.Q[{j}] -> readMux.B[{j}];
reg3.Q[{j}] -> readMux.C[{j}];
reg4.Q[{j}] -> readMux.D[{j}];
}
# Read address selects output
ReadAddr[:2] -> readMux.Sel[:2];
# Output
readMux.O[:16] -> ReadData[:16];
}
}
Testing with PySHDL
from SHDL import Circuit
with Circuit("reg16.shdl") as reg:
# Test 1: Load a value
reg.poke("D", 0xABCD)
reg.poke("Load", 1)
reg.step()
assert reg.peek("Q") == 0xABCD, "Failed to load value"
# Test 2: Value retained when Load=0
reg.poke("D", 0x1234) # New value on input
reg.poke("Load", 0) # But don't load it
reg.step()
assert reg.peek("Q") == 0xABCD, "Value not retained"
# Test 3: Load new value
reg.poke("Load", 1)
reg.step()
assert reg.peek("Q") == 0x1234, "Failed to update"
print("All register tests passed!")
Notes on Simulation
Registers create feedback loops in the circuit. SHDL's simulator handles this through iterative settling - it runs multiple tick() cycles until the circuit reaches a stable state.
For proper sequential logic with clock edges, you may need to structure your design to avoid race conditions.
Real-World Considerations
In actual hardware:
- Registers are clocked (edge-triggered)
- Setup and hold times must be respected
- Clock distribution is critical
SHDL's simplified model is great for learning the logical structure of registers without the complexity of timing analysis.