Project 14: Simple RAM (8 words x 8 bits)
Infer a small synchronous RAM and test read/write behavior.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Intermediate |
| Time Estimate | 3-4 hours |
| Main Programming Language | Verilog (Alternatives: VHDL, SystemVerilog) |
| Alternative Programming Languages | VHDL, SystemVerilog |
| Coolness Level | Medium |
| Business Potential | Medium |
| Prerequisites | Registers, Synchronous logic |
| Key Topics | Memory inference, Read latency |
1. Learning Objectives
- Infer a RAM with correct read/write semantics
- Handle read-during-write cases
- Document read latency
2. All Theory Needed (Per-Concept Breakdown)
Memory Inference and RAM Behavior
Description/Expanded Explanation of the concept
FPGAs contain block RAMs. You can infer them by writing the right Verilog pattern. Synchronous read and write behavior is common, and read-during-write behavior must be defined.
Definitions & Key Terms
- Block RAM -> dedicated on-chip memory
- Synchronous read -> data available after a clock edge
- Read-during-write -> behavior when reading and writing same address
Mental Model Diagram (ASCII)
addr -> [RAM] -> dout
^
din (we)

How It Works (Step-by-Step)
- Define a memory array in Verilog.
- Use a clocked block for writes.
- Decide and document read behavior.
- Test read-during-write cases.
Minimal Concrete Example
reg [7:0] mem [0:7];
always @(posedge clk) begin
if (we) mem[addr] <= din;
dout <= mem[addr];
end
Common Misconceptions
- “Reads are always combinational.” -> Many FPGA RAMs are synchronous.
- “Read-during-write is obvious.” -> Tools may infer different modes.
Check-Your-Understanding Questions
- What is the read latency of your RAM?
- What happens when read and write share the same address?
- How do you initialize memory for simulation?
Where You’ll Apply It
- This project: used in Section 3.2 and Section 4
- Also used in: Final CPU
Flip-Flops and Registers
Description/Expanded Explanation of the concept
Flip-flops are the fundamental storage elements in synchronous digital logic. A register is a collection of flip-flops that store multi-bit values. Correct usage requires edge-triggered logic and non-blocking assignments.
Definitions & Key Terms
- Flip-flop -> edge-triggered storage element
- Register -> N-bit storage built from flip-flops
- Reset -> initializes state to known values
Mental Model Diagram (ASCII)
clk edge -> [FF] -> q

How It Works (Step-by-Step)
- On each clock edge, the flip-flop samples D.
- The output Q holds the sampled value until the next edge.
- Reset forces Q to a known value.
Minimal Concrete Example
always @(posedge clk) begin
if (reset) q <= 0;
else q <= d;
end
Common Misconceptions
- “reg always means storage.” -> It is a variable that may infer storage.
- “Blocking assignments are fine for registers.” -> Use non-blocking.
Check-Your-Understanding Questions
- Why is non-blocking used for sequential logic?
- What is the difference between a latch and a flip-flop?
- What is the role of reset?
Where You’ll Apply It
- This project: used in Section 3.2 and Section 4
- Also used in: P07-4-bit-updown-counter-with-load.md, P08-shift-register-led-chaser.md
Verification with Testbenches and Waveforms
Description/Expanded Explanation of the concept
Testbenches are simulation-only modules that apply stimulus and check outputs. Waveforms (VCD) are the hardware engineer’s microscope; they reveal timing, glitches, and ordering problems. A good testbench is deterministic and covers edge cases.
Definitions & Key Terms
- Testbench -> a non-synthesizable module that drives a DUT
- VCD -> Value Change Dump waveform file
- Deterministic test -> same inputs produce same outputs every run
Mental Model Diagram (ASCII)
[Testbench] -> [DUT] -> [VCD] -> [GTKWave]

How It Works (Step-by-Step)
- Initialize inputs to known values.
- Apply stimulus over time.
- Dump waveforms and check outputs.
- Add assertions or PASS/FAIL messages.
Minimal Concrete Example
initial begin
$dumpfile("wave.vcd");
$dumpvars(0, tb);
a = 0; b = 1; #10;
$finish;
end
Common Misconceptions
- “If it simulates once, it’s correct.” -> Cover all relevant cases.
- “Waveforms are optional.” -> They are often the only way to debug timing.
Check-Your-Understanding Questions
- Why keep testbench and DUT separate?
- What is the purpose of
$dumpvars? - How do you make a testbench deterministic?
Where You’ll Apply It
- This project: used throughout Section 6 (testing)
- Also used in: all other projects in this folder
3. Project Specification
3.1 What You Will Build
An 8x8 RAM with synchronous write and read.
3.2 Functional Requirements
- Requirement 1: Write on clock edge when we=1
- Requirement 2: Read data with defined latency
- Requirement 3: Support all addresses 0-7
3.3 Non-Functional Requirements
- Performance: Stable operation at the target clock and interfaces.
- Reliability: Deterministic outputs on all defined inputs.
- Usability: Clear ports and documented behavior.
3.4 Example Usage / Output
{p['example_usage']}
3.5 Data Formats / Schemas / Protocols
{p[‘data_format’]}
3.6 Edge Cases
- Read during write
- Uninitialized memory
3.7 Real World Outcome
3.7.1 How to Run (Copy/Paste)
vvp ram_tb
3.7.2 Golden Path Demo (Deterministic)
Run the demo command above with the provided testbench and confirm the outputs match the golden transcript.
3.7.3 CLI Transcript
write addr=3 data=0xAA
read addr=3 -> 0xAA
3.7.4 Failure Demo (Expected)
# Example failure case
ERROR: Output mismatch at vector 3
Expected: 0x0A, Got: 0x0B
EXIT CODE: 1
Notes:
- Exit code 0 indicates all tests passed
- Exit code 1 indicates a test failure
4. Solution Architecture
4.1 High-Level Design
[inputs] -> [core logic] -> [outputs]

4.2 Key Components
| Component | Responsibility |
|---|---|
| ram | Memory array and control logic |
4.3 Data Structures (No Full Code)
// Example signals (adapt to your design)
reg [7:0] state_reg;
reg [7:0] data_reg;
4.4 Algorithm Overview
Key Algorithm: Core control flow
- Initialize state/reset conditions.
- Apply inputs and compute outputs.
- Update state on clock edges (if sequential).
Complexity Analysis:
- Time: O(1) per cycle
- Space: O(N) for registers and logic
5. Implementation Guide
5.1 Development Environment Setup
iverilog -v
# Ensure GTKWave is installed for waveform viewing
5.2 Project Structure
project-root/
|-- src/
| |-- top.v
| |-- core.v
|-- tb/
| |-- tb.v
|-- Makefile
|-- README.md

5.3 The Core Question You’re Answering
“How do you model memory so synthesis infers real RAM blocks?”
5.4 Concepts You Must Understand First
- Registers
- Synchronous logic
5.5 Questions to Guide Your Design
- Will read be synchronous or async?
- How will you handle read-during-write?
5.6 Thinking Exercise
Draw timing for write followed by read in consecutive cycles.
5.7 The Interview Questions They’ll Ask
- Why do FPGA RAMs prefer synchronous reads?
- How do you infer BRAM?
5.8 Hints in Layers
- Use a reg array with clocked write.
- Assign dout in the clocked block for sync read.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Memory | Digital Design and Computer Architecture | Ch. 5 |
5.10 Implementation Phases
Phase 1: Foundation
Goals:
- Establish core module structure
- Implement minimal behavior
Tasks:
- Scaffold module ports and internal signals
- Write a minimal testbench that compiles
Checkpoint: Simulation runs without errors
Phase 2: Core Functionality
Goals:
- Implement full logic
- Verify edge cases
Tasks:
- Complete core logic
- Add directed tests for edge cases
Checkpoint: All tests pass and waveforms match expectations
Phase 3: Polish & Edge Cases
Goals:
- Improve readability
- Document behavior
Tasks:
- Add comments and README notes
- Expand tests for unusual inputs
Checkpoint: Design is deterministic and documented
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Reset strategy | Sync / Async | Sync | Simpler timing closure |
| Test coverage | Directed / Exhaustive | Exhaustive for small logic | Prevents missed cases |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | Test core logic | Small vectors |
| Integration Tests | Test modules together | Full system |
| Edge Case Tests | Boundary conditions | Max/min values |
6.2 Critical Test Cases
- Test 1: Write/read all addresses
- Test 2: Read-during-write behavior documented
6.3 Test Data
Use deterministic vectors and document expected outputs.
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| Unexpected latency | Data appears one cycle later | Document sync read latency |
7.2 Debugging Strategies
- Inspect waveforms at key internal signals
- Add temporary debug outputs to verify state
- Reduce testcases to the smallest failing case
7.3 Performance Traps
- Overly wide counters or combinational paths can reduce max clock
8. Extensions & Challenges
8.1 Beginner Extensions
- Add parameterization for widths
- Add optional features (enable, reset)
8.2 Intermediate Extensions
- Add configuration registers
- Build a simple driver or demo program
8.3 Advanced Extensions
- Integrate with another project in this series
- Implement a hardware demo on FPGA
9. Real-World Connections
9.1 Industry Applications
- Digital control systems and embedded peripherals
- FPGA prototyping and validation
9.2 Related Open Source Projects
- Yosys / nextpnr toolchain for open-source FPGA flow
- Example HDL projects in the FPGA community
9.3 Interview Relevance
- Demonstrates RTL thinking and verification skills
10. Resources
10.1 Essential Reading
- Digital Design and Computer Architecture - Focus on Ch. 5
10.2 Video Resources
- Search for project-specific HDL walkthroughs and waveforms
10.3 Tools & Documentation
- Icarus Verilog
- GTKWave
10.4 Related Projects in This Series
- See adjacent projects in
VERILOG_FROM_ZERO_PROJECTS/
11. Self-Assessment Checklist
11.1 Understanding
- I can explain the core concept without notes
- I can predict waveform behavior for basic inputs
11.2 Implementation
- All functional requirements are met
- All tests pass
- Edge cases are documented
11.3 Growth
- I can explain this project in an interview
- I documented at least one lesson learned
12. Submission / Completion Criteria
Minimum Viable Completion:
- Functional requirements implemented
- Testbench passes
- Waveforms inspected
Full Completion:
- All minimum criteria plus
- Edge cases covered and documented
Excellence (Going Above & Beyond):
- Hardware demo on FPGA
- Clear write-up of lessons learned
Appendix A: Deep Dive Walkthrough
A.1 Signal Map and Invariants
- Inputs:
clk,we,addr[2:0],din[7:0] - Output:
dout[7:0]
Invariant: Writes occur on clock edge when we=1. Reads are synchronous (one-cycle latency).
A.2 Read-During-Write Policy
Define and document one of:
- Write-first:
doutreturns new data. - Read-first:
doutreturns old data.
Pick one and assert it in the testbench. The recommended choice is write-first for simplicity.
A.3 Deterministic Test Sequence
- Write 0xAA to addr 3.
- Write 0x55 to addr 4.
- Read addr 3 -> expect 0xAA after 1 cycle.
- Write 0xF0 to addr 3 while reading addr 3 -> verify policy.
A.4 Waveform Debug Tips
- Probe
addr,we,din,douttogether. - Check that
doutupdates only on clock edges.
13. Deep Dive Appendix
13.1 Timing and Resource Budget
- With synchronous RAM, read data appears one clock after address.
- Write timing must meet setup/hold for addr and data around the write edge.
- On FPGA, this maps to block RAM if coded correctly.
13.2 Waveform Interpretation Guide
- Verify write enable and addr are stable at the clock edge.
- Confirm that dout updates one cycle after read address.
Example sequence:
cycle 0: we=1 addr=3 din=0xAA
cycle 1: we=0 addr=3 -> dout=0xAA
13.3 Hardware Bring-Up Notes
- Use switches to set address/data and a button to pulse we.
- Display dout on LEDs or 7-seg.
- If inference fails, check that your memory is declared as
reg [7:0] mem [0:7];.
13.4 Alternate Implementations and Trade-offs
- Sync read: realistic for FPGA block RAM.
- Async read: easier for simulation, but not always synthesizable to BRAM.
- Dual-port: add separate read/write ports for higher throughput.
13.5 Additional Exercises
- Add byte enable bits for partial writes.
- Initialize memory from a hex file using
$readmemh. - Add a memory-mapped register at a specific address.