A WISHBONE B.3 compliant memory slave IP written in SystemVerilog, complete with a VIP-based testbench for functional verification.
This project implements a parameterizable, byte-addressable memory slave that speaks the WISHBONE B.3 classic bus protocol. The design is split into a clean three-layer hierarchy: a protocol controller, a memory wrapper, and a physical dual-port RAM — making each layer independently readable and reusable.
The verification environment is built around a plain-SystemVerilog VIP (Verification IP) using a mailbox-based class architecture, mirroring the style used in professional RTL verification flows.
WISHBONE-MEMORY/
├── Makefile # Vivado XSim build + simulation automation
├── interface/
│ └── wb_if.sv # WISHBONE SystemVerilog interface with bus tasks
├── source/
│ ├── wb_mem.sv # Top-level DUT (controller + memory instantiation)
│ ├── wishbone_mem_ctrlr.sv # WISHBONE B.3 protocol controller (ACK, write-enable)
│ ├── mem.sv # Memory wrapper (single-port view over dual-port RAM)
│ └── dual_port_mem.sv # Dual-port RAM with synchronous write, async read
├── vip/wb/
│ ├── wb.svh # Package include file
│ ├── cfg.sv # Configuration class (ADDR_WIDTH, DATA_WIDTH)
│ ├── seq_item.sv # Randomized sequence item with address alignment constraint
│ ├── rsp_item.sv # Response item (captured by monitor)
│ ├── driver.sv # Drives transactions onto the virtual interface
│ ├── monitor.sv # Observes and reports completed transactions
│ └── scoreboard.sv # Reference model — checks read data against expected
├── testbench/
│
│ └── wb_mem_vip_tb.sv # VIP testbench (driver + monitor + scoreboard)
├── documentation/
│ └── mem.drawio # Block diagram source (draw.io)
└── build/ # Generated by Makefile — not committed to git
wb_mem (top)
├── wishbone_mem_ctrlr — protocol layer
└── mem — memory layer
└── dual_port_mem — physical RAM
Implements the WISHBONE B.3 slave handshake. Per RULE 3.00, ACK_O and DAT_O are registered outputs — they respond exactly one clock after a valid CYC_I & STB_I phase. Write-enable to the memory is blocked combinationally during reset to prevent spurious writes.
A parameterizable dual-port RAM with separate read and write address ports. Write is synchronous (on posedge clk); read is asynchronous (combinational). Byte-lane granularity is controlled by the wstrb_i write-strobe input — only lanes with the corresponding strobe bit set are updated, leaving other bytes intact.
Address-to-row decoding strips the log2(DATA_WIDTH/8) LSBs to enforce word alignment (e.g., for a 32-bit data width, addr[1:0] are always expected to be 2'b00).
| Parameter | Default | Description |
|---|---|---|
ADDR_WIDTH |
16 |
Width of the address bus in bits |
DATA_WIDTH |
32 |
Width of the data bus in bits |
Memory depth = 2^(ADDR_WIDTH - log2(DATA_WIDTH/8)) words. With defaults: 2^14 = 16384 32-bit words = 64 KB.
The interface bundles all bus signals and exposes tasks for use in testbenches:
| Task | Description |
|---|---|
req_reset() |
Deasserts all master signals (safe idle state) |
send_write() |
Drives a complete write cycle, polls until ACK seen |
send_read() |
Drives a complete read cycle, polls until ACK seen |
look_write() |
Monitor-side: waits for a completed write transaction |
look_read() |
Monitor-side: waits for a completed read transaction |
The VIP follows a mailbox-based plain-SystemVerilog class style (no UVM).
wb_vip_tb
├── wb_driver — gets seq_items from seq_mbx, drives wb_if tasks
├── wb_monitor — watches wb_if, captures completed txns into rsp_mbx
└── wb_scoreboard — reads rsp_mbx, maintains a reference memory model,
compares read data and reports PASS / FAIL / WARN
All three components are forked with fork ... join_none and run in parallel throughout the test.
Randomized with two constraints:
addris always within the configured address space.addr[1:0] == 2'b00— enforces word alignment on every randomized transaction.
post_randomize() fills the data and sel bytes only up to the configured data width, zeroing the rest.
The scoreboard maintains an associative array (ref_mem) keyed on address. On a write, it applies the byte-strobe mask to update only the lanes that were enabled. On a read, it compares the DUT's returned data against the reference and prints [PASS], [FAIL], or [WARN] (warn = address was never written in this test run, so no reference exists yet).
| TC | Description | Expected |
|---|---|---|
| TC1 | Basic write then read-back at address 0x0000 |
PASS |
| TC2 | Write and read at a different address 0x0004 |
PASS |
| TC3 | Back-to-back writes to 3 consecutive addresses, then read all | 3× PASS |
| TC4 | Overwrite same address twice — second write wins | PASS |
| TC5 | Boundary: lowest address 0x0000 |
PASS |
| TC6 | Boundary: highest valid address 0xFFFC |
PASS |
| TC7 | Boundary: mid-range address 0x8000 |
PASS |
| TC8 | Byte enable sel=4'b0001 — only byte 0 updated |
PASS |
| TC9 | Byte enable sel=4'b1000 — only byte 3 updated |
PASS |
| TC10 | Byte enable sel=4'b1100 — upper two bytes only |
PASS |
| TC11 | Read from a never-written address | WARN (no reference — expected) |
| TC12 | Stress: write then read 10 addresses spread across full range | 10× PASS |
| TC13 | Assert reset mid-operation, verify correct behaviour after release | PASS after reset |
| TC14 | Two full write-read cycles to the same address | 2× PASS |
| TC15 | Explicit full-word write with sel=4'b1111 |
PASS |
All simulation is driven by the Makefile in the repo root. No manual Vivado project setup is needed.
# Run simulation in batch mode (default)
make
# Open Vivado waveform GUI — signals pre-loaded, run all auto-executes
make GUI=1
# Remove all build artifacts
make clean| Variable | Values | Default | Description |
|---|---|---|---|
GUI |
0 | 1 |
0 |
1 opens Vivado waveform GUI |
All XSim artifacts are isolated under build/ and never clutter the repo root:
build/
├── xsim.dir/ — compiled + elaborated snapshot
├── xvlog.log
├── xelab.log
├── xsim.log
└── wave.tcl — auto-generated waveform setup script
Add build/ to your .gitignore to keep the repo clean.
When running with GUI=1, the Makefile automatically generates build/<TB>/wave.tcl which:
- Arms signal logging for all signals recursively (
log_wave -r /) - Pre-loads signals into the wave window organized in groups with colors and radix settings
- Runs the simulation to completion (
run all) - Saves the waveform layout to
wave.wcfgin the repo root
The wave window opens with four signal groups:
| Group | Signals | Color |
|---|---|---|
| Clock and Reset | clk_i, rst_i |
Yellow / Red |
| WISHBONE Bus | cyc, stb, we, addr, data_i, sel, ack, data_o |
Cyan / Green / Orange |
| Memory Interface | mem_we, mem_addr, mem_wdata, mem_wstrb, mem_rdata |
Cyan / Green / Orange |
| DUT Outputs | dut/ack_o, dut/data_o |
Orange |
addr and data signals display in hexadecimal; sel and wstrb display in binary.
Note:
log_wave -r /may print a warning about mailbox/class handle objects that cannot be logged — this is expected and does not affect simulation correctness.
- Vivado 2020.1 or later (for
xvlog,xelab,xsim) make(GNU Make)- Vivado tools must be on your
PATH:source /tools/Xilinx/Vivado/<version>/settings64.sh
Adnan Sami Anirban
Email : adnananirban259@gmail.com