working with memory

This commit is contained in:
2026-09-02 13:09:20 +02:00
parent d4ae2417a4
commit 16769eaa4b
40 changed files with 1263404 additions and 3560 deletions
+929
View File
@@ -0,0 +1,929 @@
# FPGA-Neural — Analisi datapath e benchmark ECP5
## 1. Obiettivo
Questa fase del progetto FPGA-Neural ha avuto lo scopo di verificare il comportamento sintetizzabile e le prestazioni del core neurale parametrico sul dispositivo:
**Lattice LFE5U-45F-8BG381C**
Configurazione FPGA:
- ECP5-45F
- Speed grade: `-8`
- Package: `CABGA381`
- 72 blocchi `MULT18X18D`
- circa 43.8k LUT/FF equivalenti
La configurazione funzionale utilizzata nei benchmark è:
```text
DATA_WIDTH = 8 bit
ACC_WIDTH = 32 bit
N_INPUTS = 256
N_NEURONS = 4
PARALLEL = variabile
Il datapath implementa:
INT8 × INT8
INT16
sign extension
INT32
accumulation
+ bias
ReLU
INT8 saturation
Lo scopo principale del benchmark è stato determinare il compromesso tra:
numero di MAC paralleli;
utilizzo dei DSP;
complessità del datapath;
routing;
frequenza massima;
latenza di elaborazione.
2. Architettura RTL
L'attuale datapath è organizzato gerarchicamente:
layer
┌────────┴────────┐
│ │
neuron 0 neuron N
│ │
▼ ▼
neuron_parallel neuron_parallel
│ │
▼ ▼
mac8 mac8
│ │
MAC × PARALLEL MAC × PARALLEL
Ogni neurone elabora N_INPUTS ingressi a gruppi di PARALLEL.
Con:
N_INPUTS = 256
il numero di gruppi è:
GROUPS = 256 / PARALLEL
Pertanto:
PARALLEL Gruppi per neurone
16 16
8 32
4 64
2 128
I quattro neuroni vengono elaborati contemporaneamente.
3. MAC unit
Il modulo mac_unit implementa un singolo prodotto-accumulatore.
Per la configurazione INT8:
x : signed INT8
w : signed INT8
Il prodotto è:
INT8 × INT8 = INT16
Il risultato viene quindi esteso con segno a 32 bit:
INT16 → INT32
e sommato all'accumulatore.
L'operazione fondamentale è quindi:
acc_out = acc_in + (x × w)
L'implementazione è completamente parametrica rispetto a:
DATA_WIDTH
ACC_WIDTH
4. Balanced adder tree
Una modifica importante rispetto alla prima implementazione è stata la sostituzione dell'accumulatore combinazionale lineare con un balanced binary adder tree.
Una riduzione lineare avrebbe prodotto:
((((p0 + p1) + p2) + p3) + ...)
con profondità:
O(PARALLEL)
La nuova implementazione utilizza invece:
sum
/ \
sum sum
/ \ / \
p0 p1 p2 p3
La profondità diventa:
O(log2(PARALLEL))
Per esempio:
PARALLEL = 8
→ 3 livelli
PARALLEL = 16
→ 4 livelli
PARALLEL = 32
→ 5 livelli
Questa modifica riduce significativamente la profondità combinazionale del datapath.
5. neuron_parallel
neuron_parallel esegue il calcolo di un singolo neurone.
Il funzionamento è:
start
group 0
group 1
...
group N
+ bias
ReLU
saturation
done
Durante ogni ciclo viene elaborato un gruppo di:
PARALLEL
prodotti.
L'accumulatore mantiene il risultato tra un gruppo e il successivo.
6. Test funzionale
Il testbench sim/parametric_tb.v utilizza:
DATA_WIDTH = 8
N_INPUTS = 256
N_NEURONS = 4
PARALLEL = variabile
ACC_WIDTH = 32
Sono stati definiti quattro casi di test.
N0 — accumulazione su più gruppi
Input:
x = 1
Pesi:
primi 32 = +1
restanti = 0
Risultato:
32 × 1 × 1 = 32
Output atteso:
32
Questo test verifica soprattutto la corretta gestione dell'accumulatore attraverso più gruppi.
N1 — bias
Pesi:
tutti = 0
Bias:
+10
Output atteso:
10
N2 — ReLU
Pesi:
tutti = -1
Input:
tutti = +1
Il risultato è negativo.
La ReLU produce:
0
N3 — saturazione
Pesi:
primi 32 = +4
restanti = 0
Input:
tutti = +1
Risultato:
32 × 4 = 128
L'uscita INT8 positiva viene saturata:
128 → 127
7. Risultato simulazione
Il test è passato con PARALLEL=16:
PARALLEL = 16
PASS N0: 32
PASS N1: 10
PASS N2: 0 (ReLU)
PASS N3: 127 (saturation)
INT8 PARAMETRIC TEST PASSED
È passato anche con PARALLEL=32:
PARALLEL = 32
PASS N0: 32
PASS N1: 10
PASS N2: 0 (ReLU)
PASS N3: 127 (saturation)
INT8 PARAMETRIC TEST PASSED
La correttezza funzionale del datapath parametrico è quindi confermata.
8. Sintesi e Place & Route
Dopo la simulazione il datapath è stato sintetizzato per ECP5 utilizzando:
Yosys
e successivamente piazzato e instradato con:
nextpnr-ecp5
Target:
LFE5U-45F
CABGA381
Speed grade -8
Il wrapper di benchmark genera internamente:
input;
pesi;
bias;
segnali di test.
In questo modo non vengono portati all'esterno i giganteschi bus del modello neurale.
Il top-level espone solamente:
clk
rst
start
y_bus
busy
done
Questa modifica è stata fondamentale.
Il primo tentativo esponeva infatti direttamente:
x_bus ≈ 2048 bit
weights ≈ 8192 bit
bias ≈ 32 bit
portando a oltre 10.000 I/O fisiche richieste.
Il risultato era:
TRELLIS_IO: 10309/245
e quindi un errore di placement.
Il problema non era la dimensione logica del circuito, ma esclusivamente il numero di I/O.
9. PARALLEL = 16
Risorse sintetizzate:
LUT4 ≈ 2531
DFF = 186
Risorse FPGA:
MULT18X18D = 64 / 72
quindi:
88% dei DSP
Timing:
Fmax ≈ 52.13 MHz
Tcrit ≈ 19.18 ns
Il percorso critico risultava fortemente influenzato dal routing.
Configurazione:
PARALLEL = 16
N_NEURONS = 4
produce:
16 × 4 = 64 MAC simultanei
10. PARALLEL = 8
Risorse:
MULT18X18D = 32 / 72
quindi:
32 DSP
Con quattro neuroni:
8 × 4 = 32 MAC simultanei
Timing:
Fmax ≈ 61.71 MHz
Tcrit ≈ 16.20 ns
Composizione del percorso critico:
logic ≈ 6.43 ns
routing ≈ 9.77 ns
total ≈ 16.20 ns
Questa configurazione è particolarmente importante perché corrisponde esattamente al target originale di:
32 MAC hardware simultanei
11. PARALLEL = 4
Risorse:
LUT4 = 804
DFF = 194
MULT18X18D = 16 / 72
Quindi:
16 MAC simultanei
Timing:
Fmax ≈ 75.01 MHz
Tcrit ≈ 13.33 ns
Composizione:
logic ≈ 6.67 ns
routing ≈ 6.66 ns
total ≈ 13.33 ns
In questa configurazione logica e routing sono quasi perfettamente bilanciati.
12. PARALLEL = 2
Risorse:
LUT4 = 481
DFF = 198
MULT18X18D = 8 / 72
Quindi:
8 MAC simultanei
Timing finale dopo routing:
Fmax ≈ 87.88 MHz
Tcrit ≈ 11.38 ns
Composizione:
logic ≈ 6.43 ns
routing ≈ 4.95 ns
total ≈ 11.38 ns
Il design è stato quindi verificato con target:
80 MHz
ottenendo:
87.88 MHz
e:
PASS
Il margine teorico rispetto a 80 MHz è:
T80MHz = 12.50 ns
12.50 - 11.38 ≈ 1.12 ns
13. Tabella comparativa
PARALLEL MAC/neurone Neuroni MAC totali DSP Fmax Tcrit 80 MHz
16 16 4 64 64 52.13 MHz 19.18 ns FAIL
8 8 4 32 32 61.71 MHz 16.20 ns FAIL
4 4 4 16 16 75.01 MHz 13.33 ns FAIL
2 2 4 8 8 87.88 MHz 11.38 ns PASS
14. Interpretazione
I risultati mostrano chiaramente il trade-off fondamentale.
Riducendo PARALLEL:
PARALLEL ↓
MAC simultanei ↓
DSP ↓
adder tree ↓
routing/congestione ↓
Fmax ↑
ma contemporaneamente:
PARALLEL ↓
numero gruppi ↑
cicli necessari ↑
latenza ↑
Quindi la frequenza massima non è sufficiente per scegliere la configurazione.
Occorre considerare il throughput complessivo:
throughput ≈ MAC_per_cycle × clock_frequency
A parità di quattro neuroni:
P2
8 MAC × 87.88 MHz
≈ 703 M MAC/s
P4
16 MAC × 75.01 MHz
≈ 1.20 G MAC/s
P8
32 MAC × 61.71 MHz
≈ 1.97 G MAC/s
P16
64 MAC × 52.13 MHz
≈ 3.34 G MAC/s
Questi valori sono una misura teorica del throughput del datapath MAC, non ancora del throughput end-to-end della rete, perché non includono i limiti della memoria esterna, del trasferimento dei pesi e del controller.
15. Scelta architetturale
Il risultato più importante del benchmark è che PARALLEL=8 rimane il candidato naturale per l'architettura V1 se l'obiettivo iniziale di progetto è mantenere circa:
32 MAC simultanei
Infatti:
PARALLEL = 8
N_NEURONS = 4
→ 32 MAC
→ 32 DSP / 72
→ 61.71 MHz
L'utilizzo DSP è ancora relativamente basso:
44% circa
lasciando risorse per:
controller memoria;
buffer;
interfaccia SPI;
DMA;
gestione layer;
eventuali pipeline;
future funzioni di controllo.
PARALLEL=2 è invece la configurazione più semplice da temporizzare tra quelle testate:
87.88 MHz
e passa il target di 80 MHz.
Tuttavia richiede:
128 gruppi
per elaborare un neurone da 256 ingressi.
Per questo motivo non è opportuno adottarlo automaticamente come configurazione definitiva solo perché raggiunge la frequenza più elevata.
16. Timing a 100 MHz
Il target di:
100 MHz
non viene attualmente raggiunto.
Il miglior risultato è:
87.88 MHz
con PARALLEL=2.
Il percorso critico è ancora:
weight FF
MULT18X18D
products
adder/carry chain
acc_next
ReLU / saturation
output FF
Il problema non è quindi un'elevata occupazione delle risorse FPGA.
Al contrario, con P2 l'FPGA è utilizzato molto poco:
DSP ≈ 11%
LUT ≈ 1%
FF ≈ 0%
Il limite è principalmente temporale e dipende dal datapath combinazionale.
Per superare 100 MHz sarà probabilmente necessario introdurre una o più pipeline interne.
Questa ottimizzazione non è però ancora necessaria per procedere con la prossima fase architetturale.
17. Vincoli LPF
Durante questi benchmark il file:
synth/ecp5/top.lpf
è stato lasciato senza vincoli di pin.
nextpnr viene eseguito con:
--lpf-allow-unconstrained
Pertanto i numerosi warning relativi a I/O non vincolate sono intenzionali.
Il benchmark verifica quindi:
sintesi
placement
routing
timing
e non:
pin assignment
I/O standard della scheda
signal integrity
I vincoli LPF reali verranno aggiunti quando sarà definito il pinout della scheda FPGA-Neural.
18. Decisione per la prossima fase
Non è utile proseguire con benchmark PARALLEL=1.
Il punto di interesse architetturale è già stato individuato.
La prossima fase deve spostare il progetto dal benchmark sintetico verso l'architettura reale:
HOST
│ SPI
FPGA interface
Memory interface
┌───────────┴───────────┐
▼ ▼
PSRAM 8 MB FPGA BRAM
│ │
└──────────┬────────────┘
tile/buffer
MAC engine
accumulator
activation
output
Il core layer e neuron_parallel dovrà quindi essere separato dalla memoria fisica attraverso una Memory Interface.
19. Memoria V1
La memoria di lavoro prevista è:
ISSI IS66WVE4M16EBLL-70BLI
Caratteristiche:
64 Mbit
8 MB
4M × 16
parallel PSRAM
asynchronous/page mode
70 ns
2.73.6 V
48-TFBGA
6 × 8 mm
La memoria volatile sarà utilizzata come working memory durante l'inferenza.
La memoria persistente prevista è:
Winbond W25Q128JVS
con:
128 Mbit
16 MB
SPI NOR Flash
La divisione dei ruoli è:
W25Q128JVS
persistent storage
weights
biases
network metadata
FPGA/network configuration
e:
IS66WVE4M16EBLL
runtime working memory
input/output tensors
intermediate data
weight tiles
Infine:
FPGA BRAM
local working buffers
tiles
accumulators
20. Conclusioni
La fase di caratterizzazione del datapath ha prodotto i seguenti risultati:
Il datapath INT8/INT32 è funzionalmente corretto.
Il test parametrico è passato.
Il balanced adder tree ha sostituito con successo la precedente riduzione lineare.
Il design è sintetizzabile per LFE5U-45F.
Il placement e routing sono stati completati correttamente.
Il problema iniziale delle migliaia di I/O è stato eliminato spostando i test vector all'interno del wrapper.
PARALLEL=8 implementa esattamente 32 MAC simultanei con quattro neuroni.
PARALLEL=2 raggiunge 87.88 MHz e supera il target di 80 MHz.
Il limite attuale a 100 MHz è dovuto al percorso combinazionale, non alla saturazione delle risorse FPGA.
Non è necessario continuare il benchmark verso PARALLEL=1.
La baseline architetturale rimane quindi:
LFE5U-45F-8BG381C
INT8 / INT32
256 inputs
4 neurons
PARALLEL parametrico
con:
PARALLEL = 8
come candidato principale per la configurazione orientata al throughput e:
PARALLEL = 2
come riferimento per la configurazione orientata alla frequenza.
La prossima attività significativa è l'integrazione della Memory Interface con la PSRAM esterna, mantenendo PARALLEL come parametro del motore computazionale.
Appendice A — Toolchain
Yosys
Yosys è il tool di sintesi RTL.
Flusso:
Verilog RTL
elaborazione
ottimizzazione
mapping ECP5
JSON netlist
Versione utilizzata:
Yosys 0.68+post
Binary:
/opt/homebrew/bin/yosys
Project Trellis
Project Trellis fornisce il database open-source dell'architettura ECP5 e gli strumenti necessari all'implementazione.
Tra gli strumenti disponibili:
ecppack
ecppll
ecpbram
ecpunpack
Installazione utilizzata:
/tmp/prjtrellis/install
nextpnr-ecp5
nextpnr-ecp5 esegue:
placement
routing
timing analysis
Versione:
nextpnr-0.11.1-19-g8dbcee5
Binary:
/tmp/nextpnr/build/nextpnr-ecp5
Parametri principali:
--45k
seleziona LFE5U-45F.
--package CABGA381
seleziona il package.
--speed 8
seleziona speed grade -8.
--json
carica la netlist generata da Yosys.
--lpf
carica i vincoli di pin.
--lpf-allow-unconstrained
permette I/O non vincolate.
--freq 80
richiede un timing target di 80 MHz.
Icarus Verilog
Icarus Verilog viene utilizzato per la simulazione RTL.
Esempio:
iverilog -g2012 \
-Ptb.PARALLEL=16 \
-o sim/parametric_256x4_p16 \
sim/parametric_tb.v \
rtl/mac_unit.v \
rtl/mac8.v \
rtl/neuron_parallel.v \
rtl/layer.v
Esecuzione:
vvp sim/parametric_256x4_p16
Icarus verifica principalmente la correttezza funzionale del RTL.
Appendice B — Differenza tra simulazione e implementazione
Simulazione
Icarus Verilog
verifica:
algebra signed;
prodotti;
accumulazione;
gruppi;
bias;
ReLU;
saturazione;
segnali busy e done.
Implementazione
Yosys
+
nextpnr-ecp5
verifica:
sintetizzabilità;
mapping FPGA;
LUT;
FF;
DSP;
placement;
routing;
timing;
Fmax.
Le due verifiche sono complementari.
r
Appendice C — Stato attuale
RTL funzionale PASS
Simulazione parametrica PASS
Sintesi ECP5 PASS
Placement PASS
Routing PASS
PARALLEL=16 52.13 MHz
PARALLEL=8 61.71 MHz
PARALLEL=4 75.01 MHz
PARALLEL=2 87.88 MHz
Target 80 MHz, P2 PASS
Target 100 MHz FAIL
Memory Interface DA IMPLEMENTARE
PSRAM controller DA IMPLEMENTARE
Host SPI interface DA IMPLEMENTARE
Layer engine reale PROSSIMA FASE
+735
View File
@@ -0,0 +1,735 @@
# FPGA Neural Network Engine
Hardware Neural Network Engine based on FPGA + dedicated RAM.
The project implements a **parametric hardware accelerator for neural networks**, designed to be reusable across different embedded systems and applications.
The fundamental design principle is that the neural-network computation is performed entirely inside the FPGA, while the host system communicates with the engine through a simple hardware-independent interface such as SPI.
---
## 1. Project Goal
The goal of this project is to develop a reusable **Neural Network Engine implemented in FPGA hardware**.
The engine is composed of:
- FPGA;
- dedicated RAM connected to the FPGA;
- host interface, initially SPI and potentially Dual SPI.
The host system is not part of the neural-network computational datapath.
Possible host systems include:
- Linux SoCs;
- Raspberry-Pi-like systems;
- ESP32;
- microcontrollers;
- other embedded processors;
- development PCs.
The same Neural Network Engine architecture should therefore be usable in completely different systems.
```text
HOST SYSTEM
┌─────────────────────────┐
│ │
│ Linux / ESP32 / MCU │
│ │
│ Configuration │
│ Training │
│ Control │
└────────────┬────────────┘
SPI / Dual SPI
┌─────────────────────────┐
│ FPGA │
│ │
│ Neural Network Engine │
│ │
│ Compute / Control │
│ │
└────────────┬────────────┘
Dedicated RAM
```
---
# 2. Architectural Principle
The FPGA is the actual neural-network accelerator.
The RAM required by the neural network is physically associated with the FPGA and is **not part of the host system memory**.
The host only provides:
- configuration;
- network parameters;
- input data;
- control;
- result retrieval.
The neural-network calculations themselves are executed by the FPGA.
This separation is a fundamental architectural requirement.
---
# 3. Hardware Configuration vs Network Configuration
An important distinction is made between the **hardware architecture of the accelerator** and the **parameters of the neural network**.
## 3.1 FPGA hardware configuration
The physical architecture of the Neural Network Engine is defined when the FPGA design is synthesized and implemented.
Typical hardware parameters include:
```text
N_INPUTS
N_NEURONS
N_LAYERS
PARALLEL
DATA_WIDTH
ACCUMULATOR_WIDTH
```
These parameters can therefore be Verilog/SystemVerilog parameters or equivalent synthesis-time configuration values.
For example:
```text
N_INPUTS = 32
N_NEURONS = 4
PARALLEL = 8
```
defines a specific hardware implementation optimized for that architecture.
The resulting FPGA bitstream contains the corresponding datapath.
---
## 3.2 Neural-network configuration
Once the FPGA has been configured and initialized, the actual neural-network parameters can be loaded through the host interface.
These parameters may include:
- weights;
- biases;
- activation parameters;
- quantization parameters;
- network-specific constants.
These values are stored in the RAM associated with the FPGA.
Therefore:
```text
FPGA BITSTREAM
│ defines hardware architecture
┌─────────────────────┐
│ Neural Network │
│ Hardware Engine │
└──────────┬──────────┘
│ loads
┌─────────────────────┐
│ Dedicated RAM │
│ │
│ weights │
│ biases │
│ parameters │
│ buffers │
└─────────────────────┘
```
This provides an important separation between **hardware specialization** and **network data**.
---
# 4. Application-Specific Neural Networks
The Neural Network Engine is not intended to implement one fixed neural network.
Instead, each application can define its own network.
For example:
```text
Application A
16 inputs
8 neurons
1 output
Application B
32 inputs
16 neurons
4 outputs
Application C
64 inputs
multiple layers
custom parallelism
```
The FPGA hardware can then be generated specifically for the required architecture.
This allows the design to exploit the FPGA resources efficiently rather than implementing a completely generic and potentially inefficient neural-network processor.
---
# 5. Parametric Compute Engine
The current implementation contains a parametric neural-network layer.
A validated configuration is:
```text
N_INPUTS = 32
N_NEURONS = 4
PARALLEL = 8
```
The datapath processes the inputs in parallel groups.
Conceptually:
```text
32 inputs
├── 8 parallel MACs
├── 8 parallel MACs
├── 8 parallel MACs
└── 8 parallel MACs
Accumulation
Bias
Activation
Output
```
The architecture is intended to scale by changing the synthesis parameters.
---
# 6. Current Functional Validation
The `32 × 4 / PARALLEL = 8` configuration has been successfully simulated.
Test output:
```text
========================================
PARAMETRIC LAYER TEST
N_INPUTS = 32
N_NEURONS = 4
PARALLEL = 8
========================================
PASS N0: 8192
PASS N1: 4096
PASS N2: 0 (ReLU)
PASS N3: 16384
========================================
PARAMETRIC TEST PASSED
========================================
```
Validated functionality:
- multiple inputs;
- multiple neurons;
- parallel MAC processing;
- accumulation across multiple input groups;
- bias handling;
- independent neuron outputs;
- ReLU activation;
- parametric layer architecture.
The corresponding test has been committed to the repository.
Commit:
```text
test: validate parametric 32x4 layer with parallelism 8
```
---
# 7. FPGA Boot and Initialization
The FPGA is configured during system initialization using its normal FPGA configuration mechanism.
The FPGA bitstream defines the hardware architecture of the Neural Network Engine.
Conceptually:
```text
Power-on
FPGA configuration
│ bitstream
Neural Network Engine available
Host initialization
│ SPI
Load network parameters
Load weights / biases
Engine ready
```
This means that the host does **not dynamically construct the FPGA datapath** during normal operation.
The datapath already exists in hardware.
The host configures the network data that the datapath operates on.
---
# 8. Host Interface
The primary external interface is intended to be:
```text
SPI
```
with possible future support for:
```text
Dual SPI
```
The interface must remain independent of the host operating system.
The same hardware protocol should therefore be usable from:
```text
Linux
ESP32
MCU
PC
```
The host interface should provide access to:
- control registers;
- status;
- network configuration;
- RAM;
- input data;
- output data;
- start/stop control;
- completion status.
A conceptual command sequence is:
```text
RESET
CONFIGURE
LOAD NETWORK PARAMETERS
LOAD WEIGHTS
LOAD BIASES
LOAD INPUT
START
WAIT FOR DONE
READ OUTPUT
```
---
# 9. Dedicated FPGA RAM
The RAM is considered part of the Neural Network Engine.
It is not intended to be supplied by the host system.
Depending on the final architecture, RAM may contain:
```text
Weights
Biases
Input buffers
Intermediate layer buffers
Output buffers
Network parameters
```
The memory architecture must be designed according to:
- number of parallel MAC units;
- data width;
- required bandwidth;
- number of layers;
- buffering requirements;
- FPGA block-RAM resources;
- possible external RAM requirements.
The preferred architecture is that the FPGA directly controls this memory.
---
# 10. Training
Training and inference are conceptually separated.
The first implementation does not require the FPGA to perform the complete training process.
Training can be performed externally:
```text
PC / Linux / other host
│ training
Network weights
│ SPI
FPGA RAM
```
The FPGA then performs inference using the resulting parameters.
This approach greatly reduces the complexity of the initial hardware implementation.
However, the architecture should not prevent future implementation of hardware-assisted or fully hardware-based training.
---
# 11. Inference
During inference, the host only supplies input data and retrieves the result.
```text
HOST
Input
│ SPI
┌───────────────┐
│ FPGA │
│ │
│ Neural Network│
│ Engine │
│ │
└───────┬───────┘
Output
│ SPI
HOST
```
The host is not involved in the individual MAC operations.
This provides:
- deterministic computation;
- reduced host workload;
- hardware parallelism;
- predictable latency;
- independence from the host CPU architecture.
---
# 12. Multi-Layer Architecture
The current implementation starts from a single parametrized layer.
The intended architecture is eventually:
```text
Input
┌──────────────┐
│ Layer 0 │
└──────┬───────┘
┌──────────────┐
│ Layer 1 │
└──────┬───────┘
┌──────────────┐
│ Layer 2 │
└──────┬───────┘
Output
```
Intermediate data will be stored in FPGA-controlled memory buffers.
The number and size of layers should ultimately be part of the hardware generation process.
---
# 13. Reusability
The main purpose of the architecture is reuse.
A future project should be able to use the same general Neural Network Engine architecture with a different hardware configuration.
For example:
```text
Project A
N_INPUTS = 32
N_NEURONS = 8
PARALLEL = 8
Project B
N_INPUTS = 64
N_NEURONS = 16
PARALLEL = 16
Project C
N_INPUTS = 128
N_NEURONS = 32
PARALLEL = 32
```
The HDL architecture remains conceptually the same while synthesis parameters generate an implementation appropriate for the target application.
---
# 14. Design Philosophy
The project should be considered a:
> **Reusable FPGA Neural Network Accelerator Platform**
rather than a single neural-network implementation.
The application determines:
```text
Input size
Network topology
Number of layers
Number of neurons
Parallelism
Numerical precision
Activation functions
Memory requirements
Performance requirements
```
The hardware generator then produces the corresponding FPGA implementation.
---
# 15. Development Roadmap
## Phase 1 — Parametric Layer
- [x] Parametric inputs
- [x] Parametric neurons
- [x] Parametric parallelism
- [x] Accumulation
- [x] Bias
- [x] ReLU
- [x] 32×4 / P=8 functional test
## Phase 2 — Parameter Sweep
Validate multiple combinations of:
```text
N_INPUTS
N_NEURONS
PARALLEL
```
including configurations where the number of inputs is not an exact multiple of the parallelism.
## Phase 3 — Memory Architecture
Define:
- weight memory;
- bias memory;
- input buffers;
- output buffers;
- intermediate buffers;
- memory addressing;
- bandwidth requirements.
## Phase 4 — SPI Interface
Implement:
- SPI controller;
- register map;
- RAM access;
- configuration protocol;
- input/output protocol;
- status and control.
## Phase 5 — Multi-Layer Network
Implement:
- multiple layers;
- intermediate buffers;
- layer sequencing;
- configurable activation functions.
## Phase 6 — Host Software
Develop host-side drivers for:
- Linux;
- ESP32.
The same FPGA protocol should be usable by both.
## Phase 7 — Optimization
Evaluate:
- pipeline depth;
- MAC parallelism;
- memory bandwidth;
- numerical precision;
- FPGA resource utilization;
- latency;
- throughput.
## Phase 8 — Optional Hardware Training
Investigate:
- backpropagation;
- gradient calculation;
- weight updates;
- hardware-assisted training.
---
# 16. Long-Term Vision
The final objective is to create a reusable hardware block that can be integrated into different future MIKILAB projects.
```text
APPLICATION
┌─────────────┴─────────────┐
│ │
Linux ESP32
│ │
└─────────────┬─────────────┘
SPI / Dual SPI
┌────────────────────────┐
│ FPGA │
│ │
│ Neural Network Engine │
│ │
│ ┌────────────────────┐ │
│ │ Control │ │
│ ├────────────────────┤ │
│ │ Input Interface │ │
│ ├────────────────────┤ │
│ │ NN Compute Core │ │
│ ├────────────────────┤ │
│ │ Activation │ │
│ ├────────────────────┤ │
│ │ Output Interface │ │
│ └────────────────────┘ │
│ │
│ Dedicated RAM │
│ │
└────────────────────────┘
```
The host platform can change without changing the fundamental Neural Network Engine architecture.
The FPGA becomes a dedicated neural-computation peripheral, analogous to other hardware accelerators, but optimized specifically for the neural-network topology required by each application.
---
# 17. Current Status
| Component | Status |
|---|---|
| Parametric neuron layer | OK Working |
| Parametric input count | OK |
| Parametric neuron count | OK |
| Parametric parallelism | OK |
| Accumulation | OK |
| Bias | OK |
| ReLU | OK |
| 32×4 / P=8 validation | OK |
| Dedicated RAM architecture | - Design |
| SPI interface | Planned |
| Dual SPI | Future |
| Multi-layer engine | Planned |
| Linux host driver | Planned |
| ESP32 host driver | Planned |
| Hardware training | Future |
---
## Core architectural principle
**The FPGA implements the neural-network machine.
The FPGA owns its RAM.
The host configures and uses the machine.
The network topology is specialized at FPGA build time, while its trained parameters are loaded into FPGA-local memory at initialization.**
This separation is the foundation of the project.
+179
View File
@@ -0,0 +1,179 @@
module int8_memory_access #(
parameter ADDR_WIDTH = 22
)(
input wire clk,
input wire rst,
// ============================================================
// INT8 interface
//
// addr is BYTE address
// ============================================================
input wire req,
input wire wr,
input wire [ADDR_WIDTH-1:0] addr,
input wire signed [7:0] wdata,
output reg signed [7:0] rdata,
output reg ready,
// ============================================================
// 16-bit memory interface
// ============================================================
output reg mem_req,
output reg mem_wr,
output reg [ADDR_WIDTH-1:0] mem_addr,
output reg [15:0] mem_wdata,
output reg mem_lb_n,
output reg mem_ub_n,
input wire [15:0] mem_rdata,
input wire mem_ready
);
// ============================================================
// State machine
// ============================================================
localparam STATE_IDLE = 2'd0;
localparam STATE_WAIT = 2'd1;
reg [1:0] state;
// ============================================================
// Latched byte address
// ============================================================
reg [ADDR_WIDTH-1:0] addr_reg;
// ============================================================
// Main state machine
// ============================================================
always @(posedge clk) begin
if (rst) begin
state <= STATE_IDLE;
addr_reg <= {ADDR_WIDTH{1'b0}};
rdata <= 8'sd0;
ready <= 1'b0;
mem_req <= 1'b0;
mem_wr <= 1'b0;
mem_addr <= {ADDR_WIDTH{1'b0}};
mem_wdata <= 16'h0000;
// Active-low byte enables:
// 1 = disabled
mem_lb_n <= 1'b1;
mem_ub_n <= 1'b1;
end else begin
// ready is a one-cycle pulse
ready <= 1'b0;
// mem_req is a one-cycle pulse
mem_req <= 1'b0;
case (state)
// =================================================
// IDLE
// =================================================
STATE_IDLE: begin
if (req) begin
addr_reg <= addr;
mem_req <= 1'b1;
mem_wr <= wr;
// ------------------------------------------------
// Byte address -> 16-bit word address
//
// addr[0] = 0 -> low byte
// addr[0] = 1 -> high byte
// ------------------------------------------------
mem_addr <= addr >> 1;
// ------------------------------------------------
// Select byte
// ------------------------------------------------
if (addr[0] == 1'b0) begin
// Low byte
mem_lb_n <= 1'b0;
mem_ub_n <= 1'b1;
// Data goes into DQ[7:0]
mem_wdata <= {8'h00, wdata};
end else begin
// High byte
mem_lb_n <= 1'b1;
mem_ub_n <= 1'b0;
// Data goes into DQ[15:8]
mem_wdata <= {wdata, 8'h00};
end
state <= STATE_WAIT;
end
end
// =================================================
// WAIT
// =================================================
STATE_WAIT: begin
if (mem_ready) begin
// ------------------------------------------------
// Extract requested byte
// ------------------------------------------------
if (addr_reg[0] == 1'b0)
rdata <= mem_rdata[7:0];
else
rdata <= mem_rdata[15:8];
ready <= 1'b1;
state <= STATE_IDLE;
end
end
// =================================================
// Default
// =================================================
default: begin
state <= STATE_IDLE;
mem_req <= 1'b0;
mem_lb_n <= 1'b1;
mem_ub_n <= 1'b1;
end
endcase
end
end
endmodule
-2
View File
@@ -1,6 +1,5 @@
module layer #(
parameter DATA_WIDTH = 16,
parameter FRAC_BITS = 8,
parameter N_INPUTS = 64,
parameter N_NEURONS = 8,
parameter PARALLEL = 8,
@@ -36,7 +35,6 @@ module layer #(
neuron_parallel #(
.DATA_WIDTH(DATA_WIDTH),
.FRAC_BITS(FRAC_BITS),
.N_INPUTS(N_INPUTS),
.PARALLEL(PARALLEL),
.ACC_WIDTH(ACC_WIDTH)
+93 -98
View File
@@ -1,122 +1,117 @@
module mac8 #(
parameter DATA_WIDTH = 16,
parameter ACC_WIDTH = 40
parameter ACC_WIDTH = 40,
parameter PARALLEL = 8
)(
input signed [DATA_WIDTH*8-1:0] x_bus,
input signed [DATA_WIDTH*8-1:0] w_bus,
input signed [DATA_WIDTH*PARALLEL-1:0] x_bus,
input signed [DATA_WIDTH*PARALLEL-1:0] w_bus,
input signed [ACC_WIDTH-1:0] acc_in,
output signed [ACC_WIDTH-1:0] acc_out
);
wire signed [ACC_WIDTH-1:0] m0;
wire signed [ACC_WIDTH-1:0] m1;
wire signed [ACC_WIDTH-1:0] m2;
wire signed [ACC_WIDTH-1:0] m3;
wire signed [ACC_WIDTH-1:0] m4;
wire signed [ACC_WIDTH-1:0] m5;
wire signed [ACC_WIDTH-1:0] m6;
wire signed [ACC_WIDTH-1:0] m7;
/*
* Each MAC produces one sign-extended product.
*/
wire signed [ACC_WIDTH-1:0] products [0:PARALLEL-1];
wire signed [ACC_WIDTH-1:0] s0;
wire signed [ACC_WIDTH-1:0] s1;
wire signed [ACC_WIDTH-1:0] s2;
wire signed [ACC_WIDTH-1:0] s3;
genvar i;
wire signed [ACC_WIDTH-1:0] s4;
wire signed [ACC_WIDTH-1:0] s5;
generate
for (i = 0; i < PARALLEL; i = i + 1) begin : GEN_MAC
wire signed [ACC_WIDTH-1:0] sum;
mac_unit #(
.DATA_WIDTH(DATA_WIDTH),
.ACC_WIDTH(ACC_WIDTH)
) u_mac (
.x(
x_bus[
i*DATA_WIDTH
+:
DATA_WIDTH
]
),
.w(
w_bus[
i*DATA_WIDTH
+:
DATA_WIDTH
]
),
.acc_in({ACC_WIDTH{1'b0}}),
.acc_out(products[i])
);
mac_unit #(
.DATA_WIDTH(DATA_WIDTH),
.ACC_WIDTH(ACC_WIDTH)
) mac0 (
.x(x_bus[0*DATA_WIDTH +: DATA_WIDTH]),
.w(w_bus[0*DATA_WIDTH +: DATA_WIDTH]),
.acc_in({ACC_WIDTH{1'b0}}),
.acc_out(m0)
);
end
endgenerate
mac_unit #(
.DATA_WIDTH(DATA_WIDTH),
.ACC_WIDTH(ACC_WIDTH)
) mac1 (
.x(x_bus[1*DATA_WIDTH +: DATA_WIDTH]),
.w(w_bus[1*DATA_WIDTH +: DATA_WIDTH]),
.acc_in({ACC_WIDTH{1'b0}}),
.acc_out(m1)
);
mac_unit #(
.DATA_WIDTH(DATA_WIDTH),
.ACC_WIDTH(ACC_WIDTH)
) mac2 (
.x(x_bus[2*DATA_WIDTH +: DATA_WIDTH]),
.w(w_bus[2*DATA_WIDTH +: DATA_WIDTH]),
.acc_in({ACC_WIDTH{1'b0}}),
.acc_out(m2)
);
/*
* Balanced binary adder tree.
*
* PARALLEL is intended to be a power of two:
* 8 -> 3 levels
* 16 -> 4 levels
* 32 -> 5 levels
*
* This replaces the previous linear accumulator:
*
* (((p0+p1)+p2)+p3)+...
*
* with:
*
* sum
* / \
* ... ...
*
* reducing the combinational depth from O(PARALLEL)
* to O(log2(PARALLEL)).
*/
mac_unit #(
.DATA_WIDTH(DATA_WIDTH),
.ACC_WIDTH(ACC_WIDTH)
) mac3 (
.x(x_bus[3*DATA_WIDTH +: DATA_WIDTH]),
.w(w_bus[3*DATA_WIDTH +: DATA_WIDTH]),
.acc_in({ACC_WIDTH{1'b0}}),
.acc_out(m3)
);
localparam TREE_LEVELS = $clog2(PARALLEL);
mac_unit #(
.DATA_WIDTH(DATA_WIDTH),
.ACC_WIDTH(ACC_WIDTH)
) mac4 (
.x(x_bus[4*DATA_WIDTH +: DATA_WIDTH]),
.w(w_bus[4*DATA_WIDTH +: DATA_WIDTH]),
.acc_in({ACC_WIDTH{1'b0}}),
.acc_out(m4)
);
wire signed [ACC_WIDTH-1:0]
tree [0:TREE_LEVELS][0:PARALLEL-1];
mac_unit #(
.DATA_WIDTH(DATA_WIDTH),
.ACC_WIDTH(ACC_WIDTH)
) mac5 (
.x(x_bus[5*DATA_WIDTH +: DATA_WIDTH]),
.w(w_bus[5*DATA_WIDTH +: DATA_WIDTH]),
.acc_in({ACC_WIDTH{1'b0}}),
.acc_out(m5)
);
generate
mac_unit #(
.DATA_WIDTH(DATA_WIDTH),
.ACC_WIDTH(ACC_WIDTH)
) mac6 (
.x(x_bus[6*DATA_WIDTH +: DATA_WIDTH]),
.w(w_bus[6*DATA_WIDTH +: DATA_WIDTH]),
.acc_in({ACC_WIDTH{1'b0}}),
.acc_out(m6)
);
/*
* Level 0 = individual products
*/
for (i = 0; i < PARALLEL; i = i + 1) begin : GEN_TREE_INPUT
assign tree[0][i] = products[i];
end
mac_unit #(
.DATA_WIDTH(DATA_WIDTH),
.ACC_WIDTH(ACC_WIDTH)
) mac7 (
.x(x_bus[7*DATA_WIDTH +: DATA_WIDTH]),
.w(w_bus[7*DATA_WIDTH +: DATA_WIDTH]),
.acc_in({ACC_WIDTH{1'b0}}),
.acc_out(m7)
);
endgenerate
assign s0 = m0 + m1;
assign s1 = m2 + m3;
assign s2 = m4 + m5;
assign s3 = m6 + m7;
assign s4 = s0 + s1;
assign s5 = s2 + s3;
genvar level;
genvar node;
assign sum = s4 + s5;
generate
assign acc_out = acc_in + sum;
for (level = 0; level < TREE_LEVELS; level = level + 1) begin : GEN_TREE_LEVEL
for (
node = 0;
node < (PARALLEL >> (level + 1));
node = node + 1
) begin : GEN_TREE_NODE
assign tree[level + 1][node] =
tree[level][2*node] +
tree[level][2*node + 1];
end
end
endgenerate
/*
* Add the partial sum to the accumulator.
*/
assign acc_out =
acc_in + tree[TREE_LEVELS][0];
endmodule
+94
View File
@@ -0,0 +1,94 @@
module memory_interface #(
parameter ADDR_WIDTH = 22,
parameter DATA_WIDTH = 16
)(
input wire clk,
input wire rst,
input wire req,
input wire wr,
input wire [ADDR_WIDTH-1:0] addr,
input wire [DATA_WIDTH-1:0] wdata,
input wire lb_n,
input wire ub_n,
output reg [DATA_WIDTH-1:0] rdata,
output reg ready,
output reg mem_req,
output reg mem_wr,
output reg [ADDR_WIDTH-1:0] mem_addr,
output reg [DATA_WIDTH-1:0] mem_wdata,
output reg mem_lb_n,
output reg mem_ub_n,
input wire [DATA_WIDTH-1:0] mem_rdata,
input wire mem_ready
);
localparam STATE_IDLE = 2'd0;
localparam STATE_WAIT = 2'd1;
reg [1:0] state;
always @(posedge clk) begin
if (rst) begin
state <= STATE_IDLE;
rdata <= {DATA_WIDTH{1'b0}};
ready <= 1'b0;
mem_lb_n <= 1'b1;
mem_ub_n <= 1'b1;
mem_req <= 1'b0;
mem_wr <= 1'b0;
mem_addr <= {ADDR_WIDTH{1'b0}};
mem_wdata <= {DATA_WIDTH{1'b0}};
end else begin
// Default: pulses
ready <= 1'b0;
mem_req <= 1'b0;
case (state)
STATE_IDLE: begin
if (req) begin
// Latch transaction
mem_wr <= wr;
mem_addr <= addr;
mem_wdata <= wdata;
mem_lb_n <= lb_n;
mem_ub_n <= ub_n;
// Issue exactly one-cycle request
mem_req <= 1'b1;
state <= STATE_WAIT;
end
end
STATE_WAIT: begin
// Wait for memory completion
if (mem_ready) begin
if (!mem_wr)
rdata <= mem_rdata;
ready <= 1'b1;
state <= STATE_IDLE;
end
end
default: begin
state <= STATE_IDLE;
end
endcase
end
end
endmodule
+105
View File
@@ -0,0 +1,105 @@
module memory_model #(
parameter ADDR_WIDTH = 22,
parameter DATA_WIDTH = 16,
parameter DEPTH = 4096,
parameter READ_LATENCY = 2
)(
input wire clk,
input wire rst,
input wire req,
input wire wr,
input wire [ADDR_WIDTH-1:0] addr,
input wire [DATA_WIDTH-1:0] wdata,
output reg [DATA_WIDTH-1:0] rdata,
output reg ready
);
reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
reg busy;
reg pending_wr;
reg [ADDR_WIDTH-1:0] pending_addr;
reg [DATA_WIDTH-1:0] pending_wdata;
integer delay_count;
integer i;
always @(posedge clk) begin
if (rst) begin
rdata <= {DATA_WIDTH{1'b0}};
ready <= 1'b0;
busy <= 1'b0;
pending_wr <= 1'b0;
pending_addr <= {ADDR_WIDTH{1'b0}};
pending_wdata <= {DATA_WIDTH{1'b0}};
delay_count <= 0;
for (i = 0; i < DEPTH; i = i + 1)
mem[i] <= {DATA_WIDTH{1'b0}};
end else begin
// ready is a one-cycle pulse
ready <= 1'b0;
// ----------------------------------------------------
// Accept request
// ----------------------------------------------------
if (!busy) begin
if (req) begin
busy <= 1'b1;
pending_wr <= wr;
pending_addr <= addr;
pending_wdata <= wdata;
delay_count <= READ_LATENCY;
end
end else begin
// ------------------------------------------------
// Wait
// ------------------------------------------------
if (delay_count > 0) begin
delay_count <= delay_count - 1;
end else begin
// --------------------------------------------
// Complete transaction
// --------------------------------------------
if (pending_wr) begin
// WRITE
if (pending_addr < DEPTH)
mem[pending_addr] <= pending_wdata;
end else begin
// READ
if (pending_addr < DEPTH)
rdata <= mem[pending_addr];
else
rdata <= {DATA_WIDTH{1'b0}};
end
ready <= 1'b1;
busy <= 1'b0;
end
end
end
end
endmodule
+404
View File
@@ -0,0 +1,404 @@
`timescale 1ns/1ps
module neuron_memory #(
parameter ADDR_WIDTH = 22,
parameter DATA_WIDTH = 8,
parameter N_INPUTS = 32,
parameter PARALLEL = 8,
parameter ACC_WIDTH = 32
)(
input wire clk,
input wire rst,
input wire start,
// ------------------------------------------------------------
// Memory interface
//
// BYTE-ADDRESS / INT8 interface
// ------------------------------------------------------------
output wire mem_req,
output wire mem_wr,
output wire [ADDR_WIDTH-1:0] mem_addr,
output wire signed [7:0] mem_wdata,
input wire signed [7:0] mem_rdata,
input wire mem_ready,
// ------------------------------------------------------------
// Network memory layout
// ------------------------------------------------------------
input wire [ADDR_WIDTH-1:0] x_base,
input wire [ADDR_WIDTH-1:0] w_base,
input wire [ADDR_WIDTH-1:0] bias_addr,
// ------------------------------------------------------------
// Result
// ------------------------------------------------------------
output reg signed [7:0] y,
output reg busy,
output reg done
);
// ============================================================
// STATES
// ============================================================
localparam STATE_IDLE = 4'd0;
localparam STATE_READ_X = 4'd1;
localparam STATE_READ_W = 4'd2;
localparam STATE_READ_BIAS = 4'd3;
localparam STATE_START_N = 4'd4;
localparam STATE_WAIT_N = 4'd5;
reg [3:0] state;
reg [$clog2(N_INPUTS+1)-1:0] index;
// ============================================================
// LOCAL MEMORY ARRAYS
// ============================================================
reg signed [7:0] x_mem [0:N_INPUTS-1];
reg signed [7:0] w_mem [0:N_INPUTS-1];
reg signed [7:0] bias_reg;
// ============================================================
// NEURON BUS
// ============================================================
wire signed [DATA_WIDTH*N_INPUTS-1:0] x_bus;
wire signed [DATA_WIDTH*N_INPUTS-1:0] w_bus;
genvar i;
generate
for (i = 0; i < N_INPUTS; i = i + 1) begin : GEN_BUS
assign x_bus[i*DATA_WIDTH +: DATA_WIDTH] = x_mem[i];
assign w_bus[i*DATA_WIDTH +: DATA_WIDTH] = w_mem[i];
end
endgenerate
// ============================================================
// INT8 MEMORY ACCESS
//
// This converts BYTE addresses into 16-bit word accesses.
//
// IMPORTANT:
// The memory side of this block is connected to the EXTERNAL
// memory_interface through the neuron_memory ports.
//
// It must NOT be connected directly to the PSRAM controller.
// ============================================================
reg access_req;
reg access_wr;
reg [ADDR_WIDTH-1:0] access_addr;
reg signed [7:0] access_wdata;
wire signed [7:0] access_rdata;
wire access_ready;
wire access_mem_req;
wire access_mem_wr;
wire [ADDR_WIDTH-1:0] access_mem_addr;
wire [15:0] access_mem_wdata;
wire access_mem_lb_n;
wire access_mem_ub_n;
// ------------------------------------------------------------
// Return data from the external memory interface.
//
// memory_interface returns a 16-bit word, while neuron_memory
// exposes only the requested INT8 byte.
//
// int8_memory_access expects the complete 16-bit word.
// ------------------------------------------------------------
wire [15:0] access_mem_rdata;
assign access_mem_rdata =
access_addr[0]
? {mem_rdata, 8'h00}
: {8'h00, mem_rdata};
// ------------------------------------------------------------
// IMPORTANT:
//
// mem_ready comes from the EXTERNAL memory_interface.
// mem_rdata comes from the EXTERNAL memory_interface.
//
// This fixes the previous deadlock where int8_memory_access
// was waiting for the PSRAM controller's mem_ready directly.
// ------------------------------------------------------------
int8_memory_access #(
.ADDR_WIDTH(ADDR_WIDTH)
) u_mem (
.clk (clk),
.rst (rst),
.req (access_req),
.wr (access_wr),
.addr (access_addr),
.wdata (access_wdata),
.rdata (access_rdata),
.ready (access_ready),
.mem_req (access_mem_req),
.mem_wr (access_mem_wr),
.mem_addr (access_mem_addr),
.mem_wdata (access_mem_wdata),
.mem_lb_n (access_mem_lb_n),
.mem_ub_n (access_mem_ub_n),
.mem_rdata (access_mem_rdata),
.mem_ready (mem_ready)
);
// ============================================================
// EXTERNAL MEMORY INTERFACE
//
// The external interface expects the INT8-level signals.
// The testbench converts these into its 16-bit bus.
//
// IMPORTANT:
// access_mem_addr is already a WORD address.
// However, the external neuron_memory interface is defined
// as a BYTE address.
//
// Therefore expose the original byte address here.
// ============================================================
assign mem_req = access_mem_req;
assign mem_wr = access_mem_wr;
assign mem_addr =
access_addr;
assign mem_wdata =
access_wdata;
// ============================================================
// NEURON
// ============================================================
reg neuron_start;
wire signed [7:0] neuron_y;
wire neuron_busy;
wire neuron_done;
neuron_parallel #(
.DATA_WIDTH(DATA_WIDTH),
.N_INPUTS(N_INPUTS),
.PARALLEL(PARALLEL),
.ACC_WIDTH(ACC_WIDTH)
) u_neuron (
.clk(clk),
.rst(rst),
.start(neuron_start),
.x_bus(x_bus),
.w_bus(w_bus),
.bias(bias_reg),
.y(neuron_y),
.busy(neuron_busy),
.done(neuron_done)
);
// ============================================================
// CONTROLLER
// ============================================================
always @(posedge clk) begin
if (rst) begin
state <= STATE_IDLE;
index <= 0;
bias_reg <= 0;
access_req <= 1'b0;
access_wr <= 1'b0;
access_addr <= 0;
access_wdata <= 0;
neuron_start <= 1'b0;
y <= 0;
busy <= 1'b0;
done <= 1'b0;
end else begin
// ----------------------------------------------------
// Default pulse signals
// ----------------------------------------------------
access_req <= 1'b0;
neuron_start <= 1'b0;
done <= 1'b0;
case (state)
// =================================================
// IDLE
// =================================================
STATE_IDLE: begin
busy <= 1'b0;
if (start) begin
busy <= 1'b1;
index <= 0;
// First X byte
access_addr <= x_base;
access_wr <= 1'b0;
access_req <= 1'b1;
state <= STATE_READ_X;
end
end
// =================================================
// READ X
// =================================================
STATE_READ_X: begin
if (access_ready) begin
x_mem[index] <= access_rdata;
if (index == N_INPUTS-1) begin
index <= 0;
access_addr <= w_base;
access_wr <= 1'b0;
access_req <= 1'b1;
state <= STATE_READ_W;
end else begin
index <= index + 1'b1;
access_addr <= x_base + index + 1'b1;
access_req <= 1'b1;
end
end
end
// =================================================
// READ W
// =================================================
STATE_READ_W: begin
if (access_ready) begin
w_mem[index] <= access_rdata;
if (index == N_INPUTS-1) begin
access_addr <= bias_addr;
access_wr <= 1'b0;
access_req <= 1'b1;
state <= STATE_READ_BIAS;
end else begin
index <= index + 1'b1;
access_addr <= w_base + index + 1'b1;
access_req <= 1'b1;
end
end
end
// =================================================
// READ BIAS
// =================================================
STATE_READ_BIAS: begin
if (access_ready) begin
bias_reg <= access_rdata;
state <= STATE_START_N;
end
end
// =================================================
// START NEURON
// =================================================
STATE_START_N: begin
neuron_start <= 1'b1;
state <= STATE_WAIT_N;
end
// =================================================
// WAIT NEURON
// =================================================
STATE_WAIT_N: begin
if (neuron_done) begin
y <= neuron_y;
busy <= 1'b0;
done <= 1'b1;
state <= STATE_IDLE;
end
end
// =================================================
// DEFAULT
// =================================================
default: begin
state <= STATE_IDLE;
busy <= 1'b0;
end
endcase
end
end
endmodule
+25 -25
View File
@@ -1,9 +1,8 @@
module neuron_parallel #(
parameter DATA_WIDTH = 16,
parameter FRAC_BITS = 8,
parameter N_INPUTS = 64,
parameter DATA_WIDTH = 8,
parameter N_INPUTS = 32,
parameter PARALLEL = 8,
parameter ACC_WIDTH = 40
parameter ACC_WIDTH = 32
)(
input clk,
input rst,
@@ -31,23 +30,25 @@ module neuron_parallel #(
wire signed [ACC_WIDTH-1:0] acc_next;
wire signed [DATA_WIDTH-1:0] bias_ext_small;
wire signed [ACC_WIDTH-1:0] bias_ext;
wire signed [ACC_WIDTH-1:0] final_acc;
wire signed [ACC_WIDTH-1:0] final_value;
assign x_group =
x_bus[group_index*PARALLEL*DATA_WIDTH
+: PARALLEL*DATA_WIDTH];
x_bus[
group_index*PARALLEL*DATA_WIDTH
+: PARALLEL*DATA_WIDTH
];
assign w_group =
w_bus[group_index*PARALLEL*DATA_WIDTH
+: PARALLEL*DATA_WIDTH];
w_bus[
group_index*PARALLEL*DATA_WIDTH
+: PARALLEL*DATA_WIDTH
];
mac8 #(
.DATA_WIDTH(DATA_WIDTH),
.ACC_WIDTH(ACC_WIDTH)
.ACC_WIDTH(ACC_WIDTH),
.PARALLEL(PARALLEL)
) u_mac8 (
.x_bus(x_group),
.w_bus(w_group),
@@ -55,17 +56,12 @@ module neuron_parallel #(
.acc_out(acc_next)
);
assign bias_ext_small = bias;
// Sign extension INT8 -> INT32
assign bias_ext =
{{(ACC_WIDTH-DATA_WIDTH){bias_ext_small[DATA_WIDTH-1]}},
bias_ext_small};
{{(ACC_WIDTH-DATA_WIDTH){bias[DATA_WIDTH-1]}}, bias};
assign final_acc =
acc_next + (bias_ext <<< FRAC_BITS);
assign final_value =
final_acc >>> FRAC_BITS;
// Accumulazione finale + bias
assign final_acc = acc_next + bias_ext;
always @(posedge clk) begin
@@ -93,14 +89,18 @@ module neuron_parallel #(
acc <= final_acc;
if (final_value <= 0) begin
// ReLU
if (final_acc <= 0) begin
y <= 0;
end
else if (final_value > 32767) begin
y <= 16'sh7FFF;
// Saturazione INT32 -> INT8
else if (final_acc > 127) begin
y <= 8'sd127;
end
else begin
y <= final_value[DATA_WIDTH-1:0];
y <= final_acc[DATA_WIDTH-1:0];
end
busy <= 0;
+422
View File
@@ -0,0 +1,422 @@
module psram_controller #(
parameter ADDR_WIDTH = 22,
parameter DATA_WIDTH = 16,
parameter CLK_FREQ_MHZ = 80
)(
input wire clk,
input wire rst,
// ============================================================
// Memory Interface side
// ============================================================
input wire mem_req,
input wire mem_wr,
input wire [ADDR_WIDTH-1:0] mem_addr,
input wire [DATA_WIDTH-1:0] mem_wdata,
input wire mem_lb_n,
input wire mem_ub_n,
output reg [DATA_WIDTH-1:0] mem_rdata,
output reg mem_ready,
// ============================================================
// PSRAM physical interface
// ============================================================
output reg [ADDR_WIDTH-1:0] psram_a,
inout wire [DATA_WIDTH-1:0] psram_dq,
output reg psram_ce_n,
output reg psram_oe_n,
output reg psram_we_n,
output reg psram_lb_n,
output reg psram_ub_n,
output reg psram_zz_n
);
// ============================================================
// Timing
// ============================================================
localparam integer ACCESS_CYCLES =
((70 * CLK_FREQ_MHZ) + 999) / 1000;
localparam integer INIT_CYCLES =
150 * CLK_FREQ_MHZ;
localparam integer COUNTER_WIDTH =
(INIT_CYCLES <= 1) ? 1 : $clog2(INIT_CYCLES + 1);
// ============================================================
// State machine
// ============================================================
localparam [2:0]
STATE_INIT = 3'd0,
STATE_IDLE = 3'd1,
STATE_READ = 3'd2,
STATE_WRITE = 3'd3,
STATE_WRITE_WAIT = 3'd4;
reg [2:0] state;
reg [COUNTER_WIDTH-1:0] counter;
// ============================================================
// Latched transaction
// ============================================================
reg [ADDR_WIDTH-1:0] address_reg;
reg [DATA_WIDTH-1:0] wdata_reg;
reg wr_reg;
// ============================================================
// Latched byte enables
//
// Active LOW:
// 0 = byte enabled
// 1 = byte disabled
// ============================================================
reg lb_reg;
reg ub_reg;
// ============================================================
// PSRAM data bus control
// ============================================================
reg [DATA_WIDTH-1:0] dq_out;
reg dq_oe;
assign psram_dq =
dq_oe ? dq_out : {DATA_WIDTH{1'bz}};
// ============================================================
// Main state machine
// ============================================================
always @(posedge clk) begin
if (rst) begin
// ----------------------------------------------------
// State
// ----------------------------------------------------
state <= STATE_INIT;
counter <= 0;
// ----------------------------------------------------
// Transaction registers
// ----------------------------------------------------
address_reg <= {ADDR_WIDTH{1'b0}};
wdata_reg <= {DATA_WIDTH{1'b0}};
wr_reg <= 1'b0;
// Byte enables disabled during reset
lb_reg <= 1'b1;
ub_reg <= 1'b1;
// ----------------------------------------------------
// Memory interface
// ----------------------------------------------------
mem_rdata <= {DATA_WIDTH{1'b0}};
mem_ready <= 1'b0;
// ----------------------------------------------------
// PSRAM address
// ----------------------------------------------------
psram_a <= {ADDR_WIDTH{1'b0}};
// ----------------------------------------------------
// PSRAM control
// ----------------------------------------------------
psram_ce_n <= 1'b1;
psram_oe_n <= 1'b1;
psram_we_n <= 1'b1;
psram_lb_n <= 1'b1;
psram_ub_n <= 1'b1;
psram_zz_n <= 1'b1;
// ----------------------------------------------------
// Data bus
// ----------------------------------------------------
dq_out <= {DATA_WIDTH{1'b0}};
dq_oe <= 1'b0;
end else begin
// mem_ready is a one-cycle pulse
mem_ready <= 1'b0;
case (state)
// =================================================
// PSRAM power-up initialization
// =================================================
STATE_INIT: begin
psram_ce_n <= 1'b1;
psram_oe_n <= 1'b1;
psram_we_n <= 1'b1;
psram_lb_n <= 1'b1;
psram_ub_n <= 1'b1;
psram_zz_n <= 1'b1;
dq_oe <= 1'b0;
if (counter == INIT_CYCLES - 1) begin
counter <= 0;
state <= STATE_IDLE;
end else begin
counter <= counter + 1'b1;
end
end
// =================================================
// Idle
// =================================================
STATE_IDLE: begin
psram_ce_n <= 1'b1;
psram_oe_n <= 1'b1;
psram_we_n <= 1'b1;
psram_lb_n <= 1'b1;
psram_ub_n <= 1'b1;
psram_zz_n <= 1'b1;
dq_oe <= 1'b0;
if (mem_req) begin
// ------------------------------------------------
// Latch transaction
// ------------------------------------------------
address_reg <= mem_addr;
wdata_reg <= mem_wdata;
wr_reg <= mem_wr;
// ------------------------------------------------
// Latch byte enables
// ------------------------------------------------
lb_reg <= mem_lb_n;
ub_reg <= mem_ub_n;
// ------------------------------------------------
// Address
// ------------------------------------------------
psram_a <= mem_addr;
// ------------------------------------------------
// Apply byte enables immediately
// ------------------------------------------------
psram_lb_n <= mem_lb_n;
psram_ub_n <= mem_ub_n;
psram_ce_n <= 1'b0;
counter <= 0;
// =================================================
// WRITE
// =================================================
if (mem_wr) begin
dq_out <= mem_wdata;
dq_oe <= 1'b1;
psram_we_n <= 1'b0;
psram_oe_n <= 1'b1;
state <= STATE_WRITE;
end
// =================================================
// READ
// =================================================
else begin
dq_oe <= 1'b0;
psram_we_n <= 1'b1;
psram_oe_n <= 1'b0;
state <= STATE_READ;
end
end
end
// =================================================
// READ
// =================================================
STATE_READ: begin
psram_ce_n <= 1'b0;
psram_oe_n <= 1'b0;
psram_we_n <= 1'b1;
psram_lb_n <= lb_reg;
psram_ub_n <= ub_reg;
psram_zz_n <= 1'b1;
dq_oe <= 1'b0;
if (counter == ACCESS_CYCLES - 1) begin
// ------------------------------------------------
// Capture PSRAM data
// ------------------------------------------------
mem_rdata <= psram_dq;
mem_ready <= 1'b1;
// ------------------------------------------------
// End transaction
// ------------------------------------------------
psram_ce_n <= 1'b1;
psram_oe_n <= 1'b1;
psram_lb_n <= 1'b1;
psram_ub_n <= 1'b1;
counter <= 0;
state <= STATE_IDLE;
end else begin
counter <= counter + 1'b1;
end
end
// =================================================
// WRITE
// =================================================
STATE_WRITE: begin
psram_ce_n <= 1'b0;
psram_oe_n <= 1'b1;
psram_we_n <= 1'b0;
psram_lb_n <= lb_reg;
psram_ub_n <= ub_reg;
psram_zz_n <= 1'b1;
dq_oe <= 1'b1;
if (counter == ACCESS_CYCLES - 1) begin
// ------------------------------------------------
// End WE# pulse
// ------------------------------------------------
psram_we_n <= 1'b1;
counter <= 0;
state <= STATE_WRITE_WAIT;
end else begin
counter <= counter + 1'b1;
end
end
// =================================================
// WRITE WAIT
//
// Keep CE#/LB#/UB# active for the final write hold
// interval before releasing the transaction.
// =================================================
STATE_WRITE_WAIT: begin
psram_ce_n <= 1'b0;
psram_oe_n <= 1'b1;
psram_we_n <= 1'b1;
psram_lb_n <= lb_reg;
psram_ub_n <= ub_reg;
psram_zz_n <= 1'b1;
dq_oe <= 1'b0;
// ------------------------------------------------
// Release PSRAM
// ------------------------------------------------
psram_ce_n <= 1'b1;
psram_lb_n <= 1'b1;
psram_ub_n <= 1'b1;
// ------------------------------------------------
// Transaction complete
// ------------------------------------------------
mem_ready <= 1'b1;
state <= STATE_IDLE;
end
// =================================================
// Default recovery
// =================================================
default: begin
state <= STATE_INIT;
counter <= 0;
psram_ce_n <= 1'b1;
psram_oe_n <= 1'b1;
psram_we_n <= 1'b1;
psram_lb_n <= 1'b1;
psram_ub_n <= 1'b1;
psram_zz_n <= 1'b1;
dq_oe <= 1'b0;
lb_reg <= 1'b1;
ub_reg <= 1'b1;
end
endcase
end
end
endmodule
+954
View File
@@ -0,0 +1,954 @@
$date
Wed Sep 2 12:39:25 2026
$end
$version
Icarus Verilog
$end
$timescale
1ps
$end
$scope module tb $end
$var wire 16 ! mem_rdata [15:0] $end
$var wire 1 " mem_ready $end
$var wire 1 # ready $end
$var wire 8 $ rdata [7:0] $end
$var wire 1 % mem_wr $end
$var wire 16 & mem_wdata [15:0] $end
$var wire 1 ' mem_ub_n $end
$var wire 1 ( mem_req $end
$var wire 1 ) mem_lb_n $end
$var wire 22 * mem_addr [21:0] $end
$var parameter 32 + ADDR_WIDTH $end
$var real 1 , CLK_PERIOD $end
$var reg 22 - addr [21:0] $end
$var reg 1 . clk $end
$var reg 16 / model_rdata [15:0] $end
$var reg 1 0 model_ready $end
$var reg 1 1 req $end
$var reg 1 2 rst $end
$var reg 8 3 wdata [7:0] $end
$var reg 1 4 wr $end
$var integer 32 5 i [31:0] $end
$scope module dut $end
$var wire 22 6 addr [21:0] $end
$var wire 1 . clk $end
$var wire 16 7 mem_rdata [15:0] $end
$var wire 1 " mem_ready $end
$var wire 1 1 req $end
$var wire 1 2 rst $end
$var wire 8 8 wdata [7:0] $end
$var wire 1 4 wr $end
$var parameter 32 9 ADDR_WIDTH $end
$var parameter 2 : STATE_IDLE $end
$var parameter 2 ; STATE_WAIT $end
$var reg 22 < addr_reg [21:0] $end
$var reg 22 = mem_addr [21:0] $end
$var reg 1 ) mem_lb_n $end
$var reg 1 ( mem_req $end
$var reg 1 ' mem_ub_n $end
$var reg 16 > mem_wdata [15:0] $end
$var reg 1 % mem_wr $end
$var reg 8 ? rdata [7:0] $end
$var reg 1 # ready $end
$var reg 2 @ state [1:0] $end
$upscope $end
$scope task read_byte $end
$var reg 22 A byte_addr [21:0] $end
$var reg 8 B expected [7:0] $end
$upscope $end
$scope task write_byte $end
$var reg 22 C byte_addr [21:0] $end
$var reg 8 D data [7:0] $end
$upscope $end
$upscope $end
$enddefinitions $end
$comment Show the parameter values. $end
$dumpall
b1 ;
b0 :
b10110 9
r12.5 ,
b10110 +
$end
#0
$dumpvars
bx D
bx C
bx B
bx A
bx @
bx ?
bx >
bx =
bx <
b0 8
b0 7
b0 6
b10000000000 5
04
b0 3
12
01
00
b0 /
0.
b0 -
bx *
x)
x(
x'
bx &
x%
bx $
x#
0"
b0 !
$end
#6250
1'
1)
b0 &
b0 >
b0 *
b0 =
0%
0(
0#
b0 $
b0 ?
b0 <
b0 @
1.
#12500
0.
#18750
1.
#25000
0.
#31250
1.
#37500
0.
#43750
1.
#50000
0.
#56250
b10010 D
b0 C
02
1.
#62500
0.
#68750
11
14
b10010 3
b10010 8
1.
#75000
0.
#81250
01
b1 @
b10010 &
b10010 >
0)
1%
1(
1.
#87500
0.
#93750
1"
10
0(
1.
#100000
0.
#106250
b0 @
1#
0"
00
1.
#112500
0.
#118750
0#
b110100 D
b1 C
1.
#125000
0.
#131250
11
b110100 3
b110100 8
b1 -
b1 6
1.
#137500
0.
#143750
01
b1 @
b11010000000000 &
b11010000000000 >
0'
1)
1(
b1 <
1.
#150000
0.
#156250
0(
1"
10
1.
#162500
0.
#168750
0"
00
b0 @
1#
1.
#175000
0.
#181250
0#
b1010110 D
b10 C
1.
#187500
0.
#193750
11
b1010110 3
b1010110 8
b10 -
b10 6
1.
#200000
0.
#206250
01
b1 @
b1010110 &
b1010110 >
1'
0)
b1 *
b1 =
1(
b10 <
1.
#212500
0.
#218750
1"
10
0(
1.
#225000
0.
#231250
b0 @
1#
0"
00
1.
#237500
0.
#243750
0#
b1111000 D
b11 C
1.
#250000
0.
#256250
11
b1111000 3
b1111000 8
b11 -
b11 6
1.
#262500
0.
#268750
01
b1 @
b111100000000000 &
b111100000000000 >
0'
1)
1(
b11 <
1.
#275000
0.
#281250
0(
1"
10
1.
#287500
0.
#293750
0"
00
b0 @
1#
1.
#300000
0.
#306250
0#
b10010 B
b0 A
1.
#312500
0.
#318750
11
04
b0 -
b0 6
1.
#325000
0.
#331250
01
b1 @
b1111000 &
b1111000 >
1'
0)
b0 *
b0 =
0%
1(
b0 <
1.
#337500
0.
#343750
1"
10
b11010000010010 !
b11010000010010 7
b11010000010010 /
0(
1.
#350000
0.
#356250
b0 @
1#
b10010 $
b10010 ?
0"
00
1.
#362500
0.
#368750
0#
b110100 B
b1 A
1.
#375000
0.
#381250
11
b1 -
b1 6
1.
#387500
0.
#393750
01
b1 @
b111100000000000 &
b111100000000000 >
0'
1)
1(
b1 <
1.
#400000
0.
#406250
0(
1"
10
1.
#412500
0.
#418750
0"
00
b0 @
1#
b110100 $
b110100 ?
1.
#425000
0.
#431250
0#
b1010110 B
b10 A
1.
#437500
0.
#443750
11
b10 -
b10 6
1.
#450000
0.
#456250
01
b1 @
b1111000 &
b1111000 >
1'
0)
b1 *
b1 =
1(
b10 <
1.
#462500
0.
#468750
1"
10
b111100001010110 !
b111100001010110 7
b111100001010110 /
0(
1.
#475000
0.
#481250
b0 @
1#
b1010110 $
b1010110 ?
0"
00
1.
#487500
0.
#493750
0#
b1111000 B
b11 A
1.
#500000
0.
#506250
11
b11 -
b11 6
1.
#512500
0.
#518750
01
b1 @
b111100000000000 &
b111100000000000 >
0'
1)
1(
b11 <
1.
#525000
0.
#531250
0(
1"
10
1.
#537500
0.
#543750
0"
00
b0 @
1#
b1111000 $
b1111000 ?
1.
#550000
0.
#556250
0#
b110100 D
b10000 C
1.
#562500
0.
#568750
11
14
b110100 3
b110100 8
b10000 -
b10000 6
1.
#575000
0.
#581250
01
b1 @
b110100 &
b110100 >
1'
0)
b1000 *
b1000 =
1%
1(
b10000 <
1.
#587500
0.
#593750
1"
10
0(
1.
#600000
0.
#606250
b0 @
1#
b1010110 $
b1010110 ?
0"
00
1.
#612500
0.
#618750
0#
b10010 D
b10001 C
1.
#625000
0.
#631250
11
b10010 3
b10010 8
b10001 -
b10001 6
1.
#637500
0.
#643750
01
b1 @
b1001000000000 &
b1001000000000 >
0'
1)
1(
b10001 <
1.
#650000
0.
#656250
0(
1"
10
1.
#662500
0.
#668750
0"
00
b0 @
1#
b1111000 $
b1111000 ?
1.
#675000
0.
#681250
0#
b10101010 D
b10000 C
1.
#687500
0.
#693750
11
b10101010 3
b10101010 8
b10000 -
b10000 6
1.
#700000
0.
#706250
01
b1 @
b10101010 &
b10101010 >
1'
0)
1(
b10000 <
1.
#712500
0.
#718750
1"
10
0(
1.
#725000
0.
#731250
b0 @
1#
b1010110 $
b1010110 ?
0"
00
1.
#737500
0.
#743750
0#
b10111011 D
b10001 C
1.
#750000
0.
#756250
11
b10111011 3
b10111011 8
b10001 -
b10001 6
1.
#762500
0.
#768750
01
b1 @
b1011101100000000 &
b1011101100000000 >
0'
1)
1(
b10001 <
1.
#775000
0.
#781250
0(
1"
10
1.
#787500
0.
#793750
0"
00
b0 @
1#
b1111000 $
b1111000 ?
1.
#800000
0.
#806250
0#
b11111111 D
b100000 C
1.
#812500
0.
#818750
11
b11111111 3
b11111111 8
b100000 -
b100000 6
1.
#825000
0.
#831250
01
b1 @
b11111111 &
b11111111 >
1'
0)
b10000 *
b10000 =
1(
b100000 <
1.
#837500
0.
#843750
1"
10
0(
1.
#850000
0.
#856250
b0 @
1#
b1010110 $
b1010110 ?
0"
00
1.
#862500
0.
#868750
0#
b10000000 D
b100001 C
1.
#875000
0.
#881250
11
b10000000 3
b10000000 8
b100001 -
b100001 6
1.
#887500
0.
#893750
01
b1 @
b1000000000000000 &
b1000000000000000 >
0'
1)
1(
b100001 <
1.
#900000
0.
#906250
0(
1"
10
1.
#912500
0.
#918750
0"
00
b0 @
1#
b1111000 $
b1111000 ?
1.
#925000
0.
#931250
0#
b1111111 D
b100010 C
1.
#937500
0.
#943750
11
b1111111 3
b1111111 8
b100010 -
b100010 6
1.
#950000
0.
#956250
01
b1 @
b1111111 &
b1111111 >
1'
0)
b10001 *
b10001 =
1(
b100010 <
1.
#962500
0.
#968750
1"
10
0(
1.
#975000
0.
#981250
b0 @
1#
b1010110 $
b1010110 ?
0"
00
1.
#987500
0.
#993750
0#
b11111111 B
b100000 A
1.
#1000000
0.
#1006250
11
04
b100000 -
b100000 6
1.
#1012500
0.
#1018750
01
b1 @
b10000 *
b10000 =
0%
1(
b100000 <
1.
#1025000
0.
#1031250
0(
1"
10
b1000000011111111 !
b1000000011111111 7
b1000000011111111 /
1.
#1037500
0.
#1043750
0"
00
b0 @
1#
b11111111 $
b11111111 ?
1.
#1050000
0.
#1056250
0#
b10000000 B
b100001 A
1.
#1062500
0.
#1068750
11
b100001 -
b100001 6
1.
#1075000
0.
#1081250
01
b1 @
b111111100000000 &
b111111100000000 >
0'
1)
1(
b100001 <
1.
#1087500
0.
#1093750
1"
10
0(
1.
#1100000
0.
#1106250
b0 @
1#
b10000000 $
b10000000 ?
0"
00
1.
#1112500
0.
#1118750
0#
b1111111 B
b100010 A
1.
#1125000
0.
#1131250
11
b100010 -
b100010 6
1.
#1137500
0.
#1143750
01
b1 @
b1111111 &
b1111111 >
1'
0)
b10001 *
b10001 =
1(
b100010 <
1.
#1150000
0.
#1156250
0(
1"
10
b1111111 !
b1111111 7
b1111111 /
1.
#1162500
0.
#1168750
0"
00
b0 @
1#
b1111111 $
b1111111 ?
1.
#1175000
0.
#1181250
0#
1.
+550
View File
@@ -0,0 +1,550 @@
#! /opt/homebrew/Cellar/icarus-verilog/13.0/bin/vvp
:ivl_version "13.0 (stable)" "(v13_0)";
:ivl_delay_selection "TYPICAL";
:vpi_time_precision - 12;
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/system.vpi";
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/vhdl_sys.vpi";
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/vhdl_textio.vpi";
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/v2005_math.vpi";
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/va_math.vpi";
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/v2009.vpi";
S_0x102cce1c0 .scope package, "$unit" "$unit" 2 1;
.timescale 0 0;
S_0x102cc09e0 .scope module, "tb" "tb" 3 3;
.timescale -9 -12;
P_0x102ccf000 .param/l "ADDR_WIDTH" 1 3 5, +C4<00000000000000000000000000010110>;
P_0x102ccf040 .param/real "CLK_PERIOD" 1 3 6, Cr<m6400000000000000gfc5>; value=12.5000
L_0x102ccff60 .functor BUFZ 16, v0x74b08cdc0_0, C4<0000000000000000>, C4<0000000000000000>, C4<0000000000000000>;
L_0x102ccf620 .functor BUFZ 1, v0x74b08ce60_0, C4<0>, C4<0>, C4<0>;
v0x74b08c640_0 .var "addr", 21 0;
v0x74b08c6e0_0 .var "clk", 0 0;
v0x74b08c780_0 .var/i "i", 31 0;
v0x74b08c820_0 .net "mem_addr", 21 0, v0x102ccd820_0; 1 drivers
v0x74b08c8c0_0 .net "mem_lb_n", 0 0, v0x102ccf580_0; 1 drivers
v0x74b08c960_0 .net "mem_rdata", 15 0, L_0x102ccff60; 1 drivers
v0x74b08ca00_0 .net "mem_ready", 0 0, L_0x102ccf620; 1 drivers
v0x74b08caa0_0 .net "mem_req", 0 0, v0x102ccfb40_0; 1 drivers
v0x74b08cb40_0 .net "mem_ub_n", 0 0, v0x102ccfbe0_0; 1 drivers
v0x74b08cbe0_0 .net "mem_wdata", 15 0, v0x102ccfc80_0; 1 drivers
v0x74b08cc80_0 .net "mem_wr", 0 0, v0x102ccfd20_0; 1 drivers
v0x74b08cd20 .array "memory", 1023 0, 15 0;
v0x74b08cdc0_0 .var "model_rdata", 15 0;
v0x74b08ce60_0 .var "model_ready", 0 0;
v0x74b08cf00_0 .net/s "rdata", 7 0, v0x102ccfdc0_0; 1 drivers
v0x74b08cfa0_0 .net "ready", 0 0, v0x74b08c000_0; 1 drivers
v0x74b08d040_0 .var "req", 0 0;
v0x74b08d0e0_0 .var "rst", 0 0;
v0x74b08d180_0 .var/s "wdata", 7 0;
v0x74b08d220_0 .var "wr", 0 0;
S_0x102cc0b60 .scope module, "dut" "int8_memory_access" 3 43, 4 1 0, S_0x102cc09e0;
.timescale -9 -12;
.port_info 0 /INPUT 1 "clk";
.port_info 1 /INPUT 1 "rst";
.port_info 2 /INPUT 1 "req";
.port_info 3 /INPUT 1 "wr";
.port_info 4 /INPUT 22 "addr";
.port_info 5 /INPUT 8 "wdata";
.port_info 6 /OUTPUT 8 "rdata";
.port_info 7 /OUTPUT 1 "ready";
.port_info 8 /OUTPUT 1 "mem_req";
.port_info 9 /OUTPUT 1 "mem_wr";
.port_info 10 /OUTPUT 22 "mem_addr";
.port_info 11 /OUTPUT 16 "mem_wdata";
.port_info 12 /OUTPUT 1 "mem_lb_n";
.port_info 13 /OUTPUT 1 "mem_ub_n";
.port_info 14 /INPUT 16 "mem_rdata";
.port_info 15 /INPUT 1 "mem_ready";
P_0x102cce340 .param/l "ADDR_WIDTH" 0 4 2, +C4<00000000000000000000000000010110>;
P_0x102cce380 .param/l "STATE_IDLE" 1 4 40, C4<00>;
P_0x102cce3c0 .param/l "STATE_WAIT" 1 4 41, C4<01>;
v0x102ccb170_0 .net "addr", 21 0, v0x74b08c640_0; 1 drivers
v0x102ccd070_0 .var "addr_reg", 21 0;
v0x102ccebc0_0 .net "clk", 0 0, v0x74b08c6e0_0; 1 drivers
v0x102ccd820_0 .var "mem_addr", 21 0;
v0x102ccf580_0 .var "mem_lb_n", 0 0;
v0x102ccfa00_0 .net "mem_rdata", 15 0, L_0x102ccff60; alias, 1 drivers
v0x102ccfaa0_0 .net "mem_ready", 0 0, L_0x102ccf620; alias, 1 drivers
v0x102ccfb40_0 .var "mem_req", 0 0;
v0x102ccfbe0_0 .var "mem_ub_n", 0 0;
v0x102ccfc80_0 .var "mem_wdata", 15 0;
v0x102ccfd20_0 .var "mem_wr", 0 0;
v0x102ccfdc0_0 .var/s "rdata", 7 0;
v0x74b08c000_0 .var "ready", 0 0;
v0x74b08c0a0_0 .net "req", 0 0, v0x74b08d040_0; 1 drivers
v0x74b08c140_0 .net "rst", 0 0, v0x74b08d0e0_0; 1 drivers
v0x74b08c1e0_0 .var "state", 1 0;
v0x74b08c280_0 .net/s "wdata", 7 0, v0x74b08d180_0; 1 drivers
v0x74b08c320_0 .net "wr", 0 0, v0x74b08d220_0; 1 drivers
E_0x74ac1b040 .event posedge, v0x102ccebc0_0;
S_0x102cd1180 .scope task, "read_byte" "read_byte" 3 174, 3 174 0, S_0x102cc09e0;
.timescale -9 -12;
v0x74b08c3c0_0 .var "byte_addr", 21 0;
v0x74b08c460_0 .var/s "expected", 7 0;
E_0x74ac1b080 .event anyedge, v0x74b08c000_0;
TD_tb.read_byte ;
%wait E_0x74ac1b040;
%load/vec4 v0x74b08c3c0_0;
%assign/vec4 v0x74b08c640_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x74b08d220_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x74b08d040_0, 0;
%wait E_0x74ac1b040;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x74b08d040_0, 0;
T_0.0 ;
%load/vec4 v0x74b08cfa0_0;
%cmpi/ne 1, 0, 1;
%jmp/0xz T_0.1, 6;
%wait E_0x74ac1b080;
%jmp T_0.0;
T_0.1 ;
%load/vec4 v0x74b08cf00_0;
%load/vec4 v0x74b08c460_0;
%cmp/ne;
%jmp/0xz T_0.2, 6;
%vpi_call/w 3 195 "$display", "READ BYTE addr=0x%08x FAIL got=0x%02x expected=0x%02x", v0x74b08c3c0_0, v0x74b08cf00_0, v0x74b08c460_0 {0 0 0};
%vpi_call/w 3 202 "$fatal" {0 0 0};
%jmp T_0.3;
T_0.2 ;
%vpi_call/w 3 206 "$display", "READ BYTE addr=0x%08x data=0x%02x PASS", v0x74b08c3c0_0, v0x74b08cf00_0 {0 0 0};
T_0.3 ;
%wait E_0x74ac1b040;
%end;
S_0x102cd1300 .scope task, "write_byte" "write_byte" 3 138, 3 138 0, S_0x102cc09e0;
.timescale -9 -12;
v0x74b08c500_0 .var "byte_addr", 21 0;
v0x74b08c5a0_0 .var/s "data", 7 0;
TD_tb.write_byte ;
%wait E_0x74ac1b040;
%load/vec4 v0x74b08c500_0;
%assign/vec4 v0x74b08c640_0, 0;
%load/vec4 v0x74b08c5a0_0;
%assign/vec4 v0x74b08d180_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x74b08d220_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x74b08d040_0, 0;
%wait E_0x74ac1b040;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x74b08d040_0, 0;
T_1.4 ;
%load/vec4 v0x74b08cfa0_0;
%cmpi/ne 1, 0, 1;
%jmp/0xz T_1.5, 6;
%wait E_0x74ac1b080;
%jmp T_1.4;
T_1.5 ;
%vpi_call/w 3 158 "$display", "WRITE BYTE addr=0x%08x data=0x%02x PASS", v0x74b08c500_0, v0x74b08c5a0_0 {0 0 0};
%wait E_0x74ac1b040;
%end;
.scope S_0x102cc0b60;
T_2 ;
%wait E_0x74ac1b040;
%load/vec4 v0x74b08c140_0;
%flag_set/vec4 8;
%jmp/0xz T_2.0, 8;
%pushi/vec4 0, 0, 2;
%assign/vec4 v0x74b08c1e0_0, 0;
%pushi/vec4 0, 0, 22;
%assign/vec4 v0x102ccd070_0, 0;
%pushi/vec4 0, 0, 8;
%assign/vec4 v0x102ccfdc0_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x74b08c000_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x102ccfb40_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x102ccfd20_0, 0;
%pushi/vec4 0, 0, 22;
%assign/vec4 v0x102ccd820_0, 0;
%pushi/vec4 0, 0, 16;
%assign/vec4 v0x102ccfc80_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x102ccf580_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x102ccfbe0_0, 0;
%jmp T_2.1;
T_2.0 ;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x74b08c000_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x102ccfb40_0, 0;
%load/vec4 v0x74b08c1e0_0;
%dup/vec4;
%pushi/vec4 0, 0, 2;
%cmp/u;
%jmp/1 T_2.2, 6;
%dup/vec4;
%pushi/vec4 1, 0, 2;
%cmp/u;
%jmp/1 T_2.3, 6;
%pushi/vec4 0, 0, 2;
%assign/vec4 v0x74b08c1e0_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x102ccfb40_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x102ccf580_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x102ccfbe0_0, 0;
%jmp T_2.5;
T_2.2 ;
%load/vec4 v0x74b08c0a0_0;
%flag_set/vec4 8;
%jmp/0xz T_2.6, 8;
%load/vec4 v0x102ccb170_0;
%assign/vec4 v0x102ccd070_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x102ccfb40_0, 0;
%load/vec4 v0x74b08c320_0;
%assign/vec4 v0x102ccfd20_0, 0;
%load/vec4 v0x102ccb170_0;
%ix/load 4, 1, 0;
%flag_set/imm 4, 0;
%shiftr 4;
%assign/vec4 v0x102ccd820_0, 0;
%load/vec4 v0x102ccb170_0;
%parti/s 1, 0, 2;
%cmpi/e 0, 0, 1;
%jmp/0xz T_2.8, 4;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x102ccf580_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x102ccfbe0_0, 0;
%pushi/vec4 0, 0, 8;
%load/vec4 v0x74b08c280_0;
%concat/vec4; draw_concat_vec4
%assign/vec4 v0x102ccfc80_0, 0;
%jmp T_2.9;
T_2.8 ;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x102ccf580_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x102ccfbe0_0, 0;
%load/vec4 v0x74b08c280_0;
%concati/vec4 0, 0, 8;
%assign/vec4 v0x102ccfc80_0, 0;
T_2.9 ;
%pushi/vec4 1, 0, 2;
%assign/vec4 v0x74b08c1e0_0, 0;
T_2.6 ;
%jmp T_2.5;
T_2.3 ;
%load/vec4 v0x102ccfaa0_0;
%flag_set/vec4 8;
%jmp/0xz T_2.10, 8;
%load/vec4 v0x102ccd070_0;
%parti/s 1, 0, 2;
%cmpi/e 0, 0, 1;
%jmp/0xz T_2.12, 4;
%load/vec4 v0x102ccfa00_0;
%parti/s 8, 0, 2;
%assign/vec4 v0x102ccfdc0_0, 0;
%jmp T_2.13;
T_2.12 ;
%load/vec4 v0x102ccfa00_0;
%parti/s 8, 8, 5;
%assign/vec4 v0x102ccfdc0_0, 0;
T_2.13 ;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x74b08c000_0, 0;
%pushi/vec4 0, 0, 2;
%assign/vec4 v0x74b08c1e0_0, 0;
T_2.10 ;
%jmp T_2.5;
T_2.5 ;
%pop/vec4 1;
T_2.1 ;
%jmp T_2;
.thread T_2;
.scope S_0x102cc09e0;
T_3 ;
%pushi/vec4 0, 0, 1;
%store/vec4 v0x74b08c6e0_0, 0, 1;
T_3.0 ;
%delay 6250, 0;
%load/vec4 v0x74b08c6e0_0;
%inv;
%store/vec4 v0x74b08c6e0_0, 0, 1;
%jmp T_3.0;
T_3.1 ;
%end;
.thread T_3;
.scope S_0x102cc09e0;
T_4 ;
%vpi_call/w 3 99 "$dumpfile", "sim/int8_memory_access.vcd" {0 0 0};
%vpi_call/w 3 100 "$dumpvars", 32'sb00000000000000000000000000000000, S_0x102cc09e0 {0 0 0};
%end;
.thread T_4;
.scope S_0x102cc09e0;
T_5 ;
%wait E_0x74ac1b040;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x74b08ce60_0, 0;
%load/vec4 v0x74b08caa0_0;
%flag_set/vec4 8;
%jmp/0xz T_5.0, 8;
%load/vec4 v0x74b08cc80_0;
%flag_set/vec4 8;
%jmp/0xz T_5.2, 8;
%load/vec4 v0x74b08c8c0_0;
%nor/r;
%flag_set/vec4 8;
%jmp/0xz T_5.4, 8;
%load/vec4 v0x74b08cbe0_0;
%parti/s 8, 0, 2;
%ix/getv 3, v0x74b08c820_0;
%ix/load 4, 0, 0; Constant delay
%assign/vec4/a/d v0x74b08cd20, 0, 4;
T_5.4 ;
%load/vec4 v0x74b08cb40_0;
%nor/r;
%flag_set/vec4 8;
%jmp/0xz T_5.6, 8;
%load/vec4 v0x74b08cbe0_0;
%parti/s 8, 8, 5;
%ix/getv 3, v0x74b08c820_0;
%ix/load 4, 8, 0; part off
%ix/load 5, 0, 0; Constant delay
%assign/vec4/a/d v0x74b08cd20, 4, 5;
T_5.6 ;
%jmp T_5.3;
T_5.2 ;
%ix/getv 4, v0x74b08c820_0;
%load/vec4a v0x74b08cd20, 4;
%assign/vec4 v0x74b08cdc0_0, 0;
T_5.3 ;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x74b08ce60_0, 0;
T_5.0 ;
%jmp T_5;
.thread T_5;
.scope S_0x102cc09e0;
T_6 ;
%pushi/vec4 0, 0, 1;
%store/vec4 v0x74b08d040_0, 0, 1;
%pushi/vec4 0, 0, 1;
%store/vec4 v0x74b08d220_0, 0, 1;
%pushi/vec4 0, 0, 22;
%store/vec4 v0x74b08c640_0, 0, 22;
%pushi/vec4 0, 0, 8;
%store/vec4 v0x74b08d180_0, 0, 8;
%pushi/vec4 0, 0, 16;
%store/vec4 v0x74b08cdc0_0, 0, 16;
%pushi/vec4 0, 0, 1;
%store/vec4 v0x74b08ce60_0, 0, 1;
%pushi/vec4 0, 0, 32;
%store/vec4 v0x74b08c780_0, 0, 32;
T_6.0 ; Top of for-loop
%load/vec4 v0x74b08c780_0;
%cmpi/s 1024, 0, 32;
%jmp/0xz T_6.1, 5;
%pushi/vec4 0, 0, 16;
%ix/getv/s 4, v0x74b08c780_0;
%store/vec4a v0x74b08cd20, 4, 0;
T_6.2 ; for-loop step statement
%load/vec4 v0x74b08c780_0;
%addi 1, 0, 32;
%store/vec4 v0x74b08c780_0, 0, 32;
%jmp T_6.0;
T_6.1 ; for-loop exit label
%pushi/vec4 1, 0, 1;
%store/vec4 v0x74b08d0e0_0, 0, 1;
%pushi/vec4 5, 0, 32;
T_6.3 %dup/vec4;
%cmpi/s 0, 0, 32;
%jmp/1xz T_6.4, 5;
%jmp/1 T_6.4, 4;
%subi 1, 0, 32;
%wait E_0x74ac1b040;
%jmp T_6.3;
T_6.4 ;
%pop/vec4 1;
%pushi/vec4 0, 0, 1;
%store/vec4 v0x74b08d0e0_0, 0, 1;
%vpi_call/w 3 244 "$display", "\000" {0 0 0};
%vpi_call/w 3 245 "$display", "========================================" {0 0 0};
%vpi_call/w 3 246 "$display", "INT8 MEMORY ACCESS TEST" {0 0 0};
%vpi_call/w 3 247 "$display", "========================================" {0 0 0};
%vpi_call/w 3 248 "$display", "\000" {0 0 0};
%pushi/vec4 0, 0, 22;
%store/vec4 v0x74b08c500_0, 0, 22;
%pushi/vec4 18, 0, 8;
%store/vec4 v0x74b08c5a0_0, 0, 8;
%fork TD_tb.write_byte, S_0x102cd1300;
%join;
%pushi/vec4 1, 0, 22;
%store/vec4 v0x74b08c500_0, 0, 22;
%pushi/vec4 52, 0, 8;
%store/vec4 v0x74b08c5a0_0, 0, 8;
%fork TD_tb.write_byte, S_0x102cd1300;
%join;
%pushi/vec4 2, 0, 22;
%store/vec4 v0x74b08c500_0, 0, 22;
%pushi/vec4 86, 0, 8;
%store/vec4 v0x74b08c5a0_0, 0, 8;
%fork TD_tb.write_byte, S_0x102cd1300;
%join;
%pushi/vec4 3, 0, 22;
%store/vec4 v0x74b08c500_0, 0, 22;
%pushi/vec4 120, 0, 8;
%store/vec4 v0x74b08c5a0_0, 0, 8;
%fork TD_tb.write_byte, S_0x102cd1300;
%join;
%pushi/vec4 0, 0, 22;
%store/vec4 v0x74b08c3c0_0, 0, 22;
%pushi/vec4 18, 0, 8;
%store/vec4 v0x74b08c460_0, 0, 8;
%fork TD_tb.read_byte, S_0x102cd1180;
%join;
%pushi/vec4 1, 0, 22;
%store/vec4 v0x74b08c3c0_0, 0, 22;
%pushi/vec4 52, 0, 8;
%store/vec4 v0x74b08c460_0, 0, 8;
%fork TD_tb.read_byte, S_0x102cd1180;
%join;
%pushi/vec4 2, 0, 22;
%store/vec4 v0x74b08c3c0_0, 0, 22;
%pushi/vec4 86, 0, 8;
%store/vec4 v0x74b08c460_0, 0, 8;
%fork TD_tb.read_byte, S_0x102cd1180;
%join;
%pushi/vec4 3, 0, 22;
%store/vec4 v0x74b08c3c0_0, 0, 22;
%pushi/vec4 120, 0, 8;
%store/vec4 v0x74b08c460_0, 0, 8;
%fork TD_tb.read_byte, S_0x102cd1180;
%join;
%ix/load 4, 0, 0;
%flag_set/imm 4, 0;
%load/vec4a v0x74b08cd20, 4;
%cmpi/ne 13330, 0, 16;
%jmp/0xz T_6.5, 6;
%vpi_call/w 3 276 "$display", "PACKING ERROR word[0]=0x%04x expected=0x3412", &A<v0x74b08cd20, 0> {0 0 0};
%vpi_call/w 3 281 "$fatal" {0 0 0};
%jmp T_6.6;
T_6.5 ;
%vpi_call/w 3 285 "$display", "PACKING word[0] = 0x%04x PASS", &A<v0x74b08cd20, 0> {0 0 0};
T_6.6 ;
%ix/load 4, 1, 0;
%flag_set/imm 4, 0;
%load/vec4a v0x74b08cd20, 4;
%cmpi/ne 30806, 0, 16;
%jmp/0xz T_6.7, 6;
%vpi_call/w 3 294 "$display", "PACKING ERROR word[1]=0x%04x expected=0x7856", &A<v0x74b08cd20, 1> {0 0 0};
%vpi_call/w 3 299 "$fatal" {0 0 0};
%jmp T_6.8;
T_6.7 ;
%vpi_call/w 3 303 "$display", "PACKING word[1] = 0x%04x PASS", &A<v0x74b08cd20, 1> {0 0 0};
T_6.8 ;
%pushi/vec4 16, 0, 22;
%store/vec4 v0x74b08c500_0, 0, 22;
%pushi/vec4 52, 0, 8;
%store/vec4 v0x74b08c5a0_0, 0, 8;
%fork TD_tb.write_byte, S_0x102cd1300;
%join;
%pushi/vec4 17, 0, 22;
%store/vec4 v0x74b08c500_0, 0, 22;
%pushi/vec4 18, 0, 8;
%store/vec4 v0x74b08c5a0_0, 0, 8;
%fork TD_tb.write_byte, S_0x102cd1300;
%join;
%ix/load 4, 8, 0;
%flag_set/imm 4, 0;
%load/vec4a v0x74b08cd20, 4;
%cmpi/ne 4660, 0, 16;
%jmp/0xz T_6.9, 6;
%vpi_call/w 3 320 "$display", "INITIAL WORD ERROR = 0x%04x", &A<v0x74b08cd20, 8> {0 0 0};
%vpi_call/w 3 325 "$fatal" {0 0 0};
T_6.9 ;
%pushi/vec4 16, 0, 22;
%store/vec4 v0x74b08c500_0, 0, 22;
%pushi/vec4 170, 0, 8;
%store/vec4 v0x74b08c5a0_0, 0, 8;
%fork TD_tb.write_byte, S_0x102cd1300;
%join;
%ix/load 4, 8, 0;
%flag_set/imm 4, 0;
%load/vec4a v0x74b08cd20, 4;
%cmpi/ne 4778, 0, 16;
%jmp/0xz T_6.11, 6;
%vpi_call/w 3 336 "$display", "LOW BYTE PRESERVE ERROR = 0x%04x expected=0x12AA", &A<v0x74b08cd20, 8> {0 0 0};
%vpi_call/w 3 341 "$fatal" {0 0 0};
%jmp T_6.12;
T_6.11 ;
%vpi_call/w 3 345 "$display", "LOW BYTE PRESERVE 0x1234 -> 0x12AA PASS" {0 0 0};
T_6.12 ;
%pushi/vec4 17, 0, 22;
%store/vec4 v0x74b08c500_0, 0, 22;
%pushi/vec4 187, 0, 8;
%store/vec4 v0x74b08c5a0_0, 0, 8;
%fork TD_tb.write_byte, S_0x102cd1300;
%join;
%ix/load 4, 8, 0;
%flag_set/imm 4, 0;
%load/vec4a v0x74b08cd20, 4;
%cmpi/ne 48042, 0, 16;
%jmp/0xz T_6.13, 6;
%vpi_call/w 3 358 "$display", "HIGH BYTE PRESERVE ERROR = 0x%04x expected=0xBBAA", &A<v0x74b08cd20, 8> {0 0 0};
%vpi_call/w 3 363 "$fatal" {0 0 0};
%jmp T_6.14;
T_6.13 ;
%vpi_call/w 3 367 "$display", "HIGH BYTE PRESERVE 0x12AA -> 0xBBAA PASS" {0 0 0};
T_6.14 ;
%pushi/vec4 32, 0, 22;
%store/vec4 v0x74b08c500_0, 0, 22;
%pushi/vec4 255, 0, 8;
%store/vec4 v0x74b08c5a0_0, 0, 8;
%fork TD_tb.write_byte, S_0x102cd1300;
%join;
%pushi/vec4 33, 0, 22;
%store/vec4 v0x74b08c500_0, 0, 22;
%pushi/vec4 128, 0, 8;
%store/vec4 v0x74b08c5a0_0, 0, 8;
%fork TD_tb.write_byte, S_0x102cd1300;
%join;
%pushi/vec4 34, 0, 22;
%store/vec4 v0x74b08c500_0, 0, 22;
%pushi/vec4 127, 0, 8;
%store/vec4 v0x74b08c5a0_0, 0, 8;
%fork TD_tb.write_byte, S_0x102cd1300;
%join;
%pushi/vec4 32, 0, 22;
%store/vec4 v0x74b08c3c0_0, 0, 22;
%pushi/vec4 255, 0, 8;
%store/vec4 v0x74b08c460_0, 0, 8;
%fork TD_tb.read_byte, S_0x102cd1180;
%join;
%pushi/vec4 33, 0, 22;
%store/vec4 v0x74b08c3c0_0, 0, 22;
%pushi/vec4 128, 0, 8;
%store/vec4 v0x74b08c460_0, 0, 8;
%fork TD_tb.read_byte, S_0x102cd1180;
%join;
%pushi/vec4 34, 0, 22;
%store/vec4 v0x74b08c3c0_0, 0, 22;
%pushi/vec4 127, 0, 8;
%store/vec4 v0x74b08c460_0, 0, 8;
%fork TD_tb.read_byte, S_0x102cd1180;
%join;
%vpi_call/w 3 389 "$display", "\000" {0 0 0};
%vpi_call/w 3 390 "$display", "========================================" {0 0 0};
%vpi_call/w 3 391 "$display", "INT8 MEMORY ACCESS TEST PASSED" {0 0 0};
%vpi_call/w 3 392 "$display", "BYTE ADDRESSING : PASS" {0 0 0};
%vpi_call/w 3 393 "$display", "LOW BYTE : PASS" {0 0 0};
%vpi_call/w 3 394 "$display", "HIGH BYTE : PASS" {0 0 0};
%vpi_call/w 3 395 "$display", "INT8 PACKING : PASS" {0 0 0};
%vpi_call/w 3 396 "$display", "BYTE PRESERVATION : PASS" {0 0 0};
%vpi_call/w 3 397 "$display", "SIGNED INT8 : PASS" {0 0 0};
%vpi_call/w 3 398 "$display", "========================================" {0 0 0};
%vpi_call/w 3 399 "$display", "\000" {0 0 0};
%vpi_call/w 3 401 "$finish" {0 0 0};
%end;
.thread T_6;
# The file index is used to find the file name in the following table.
:file_names 5;
"N/A";
"<interactive>";
"-";
"sim/int8_memory_access_tb.v";
"rtl/int8_memory_access.v";
+405
View File
@@ -0,0 +1,405 @@
`timescale 1ns/1ps
module tb;
localparam ADDR_WIDTH = 22;
localparam CLK_PERIOD = 12.5;
reg clk;
reg rst;
// ============================================================
// INT8 interface
// ============================================================
reg req;
reg wr;
reg [ADDR_WIDTH-1:0] addr;
reg signed [7:0] wdata;
wire signed [7:0] rdata;
wire ready;
// ============================================================
// Memory interface
// ============================================================
wire mem_req;
wire mem_wr;
wire [ADDR_WIDTH-1:0] mem_addr;
wire [15:0] mem_wdata;
wire mem_lb_n;
wire mem_ub_n;
wire [15:0] mem_rdata;
wire mem_ready;
// ============================================================
// DUT
// ============================================================
int8_memory_access #(
.ADDR_WIDTH(ADDR_WIDTH)
) dut (
.clk (clk),
.rst (rst),
.req (req),
.wr (wr),
.addr (addr),
.wdata (wdata),
.rdata (rdata),
.ready (ready),
.mem_req (mem_req),
.mem_wr (mem_wr),
.mem_addr (mem_addr),
.mem_wdata (mem_wdata),
.mem_lb_n (mem_lb_n),
.mem_ub_n (mem_ub_n),
.mem_rdata (mem_rdata),
.mem_ready (mem_ready)
);
// ============================================================
// Simple memory model
// ============================================================
reg [15:0] memory [0:1023];
reg [15:0] model_rdata;
reg model_ready;
integer i;
assign mem_rdata = model_rdata;
assign mem_ready = model_ready;
// ============================================================
// Clock
// ============================================================
initial begin
clk = 1'b0;
forever #(CLK_PERIOD / 2.0)
clk = ~clk;
end
// ============================================================
// VCD
// ============================================================
initial begin
$dumpfile("sim/int8_memory_access.vcd");
$dumpvars(0, tb);
end
// ============================================================
// Memory model
// ============================================================
always @(posedge clk) begin
model_ready <= 1'b0;
if (mem_req) begin
if (mem_wr) begin
if (!mem_lb_n)
memory[mem_addr][7:0] <= mem_wdata[7:0];
if (!mem_ub_n)
memory[mem_addr][15:8] <= mem_wdata[15:8];
end else begin
model_rdata <= memory[mem_addr];
end
model_ready <= 1'b1;
end
end
// ============================================================
// Write helper
// ============================================================
task write_byte;
input [ADDR_WIDTH-1:0] byte_addr;
input signed [7:0] data;
begin
@(posedge clk);
addr <= byte_addr;
wdata <= data;
wr <= 1'b1;
req <= 1'b1;
@(posedge clk);
req <= 1'b0;
wait (ready);
$display(
"WRITE BYTE addr=0x%08x data=0x%02x PASS",
byte_addr,
data
);
@(posedge clk);
end
endtask
// ============================================================
// Read helper
// ============================================================
task read_byte;
input [ADDR_WIDTH-1:0] byte_addr;
input signed [7:0] expected;
begin
@(posedge clk);
addr <= byte_addr;
wr <= 1'b0;
req <= 1'b1;
@(posedge clk);
req <= 1'b0;
wait (ready);
if (rdata !== expected) begin
$display(
"READ BYTE addr=0x%08x FAIL got=0x%02x expected=0x%02x",
byte_addr,
rdata,
expected
);
$fatal;
end else begin
$display(
"READ BYTE addr=0x%08x data=0x%02x PASS",
byte_addr,
rdata
);
end
@(posedge clk);
end
endtask
// ============================================================
// Test
// ============================================================
initial begin
req = 1'b0;
wr = 1'b0;
addr = 0;
wdata = 0;
model_rdata = 16'h0000;
model_ready = 1'b0;
for (i = 0; i < 1024; i = i + 1)
memory[i] = 16'h0000;
rst = 1'b1;
repeat (5)
@(posedge clk);
rst = 1'b0;
$display("");
$display("========================================");
$display("INT8 MEMORY ACCESS TEST");
$display("========================================");
$display("");
// ========================================================
// Basic byte writes
// ========================================================
write_byte(22'h000000, 8'h12);
write_byte(22'h000001, 8'h34);
write_byte(22'h000002, 8'h56);
write_byte(22'h000003, 8'h78);
// ========================================================
// Reads
// ========================================================
read_byte(22'h000000, 8'h12);
read_byte(22'h000001, 8'h34);
read_byte(22'h000002, 8'h56);
read_byte(22'h000003, 8'h78);
// ========================================================
// Verify physical packing
// ========================================================
if (memory[0] !== 16'h3412) begin
$display(
"PACKING ERROR word[0]=0x%04x expected=0x3412",
memory[0]
);
$fatal;
end else begin
$display(
"PACKING word[0] = 0x%04x PASS",
memory[0]
);
end
if (memory[1] !== 16'h7856) begin
$display(
"PACKING ERROR word[1]=0x%04x expected=0x7856",
memory[1]
);
$fatal;
end else begin
$display(
"PACKING word[1] = 0x%04x PASS",
memory[1]
);
end
// ========================================================
// Byte preservation test
// ========================================================
// Start with 0x1234
write_byte(22'h000010, 8'h34);
write_byte(22'h000011, 8'h12);
if (memory[8] !== 16'h1234) begin
$display(
"INITIAL WORD ERROR = 0x%04x",
memory[8]
);
$fatal;
end
// Change low byte only:
// 0x1234 -> 0x12AA
write_byte(22'h000010, 8'hAA);
if (memory[8] !== 16'h12AA) begin
$display(
"LOW BYTE PRESERVE ERROR = 0x%04x expected=0x12AA",
memory[8]
);
$fatal;
end else begin
$display(
"LOW BYTE PRESERVE 0x1234 -> 0x12AA PASS"
);
end
// Change high byte only:
// 0x12AA -> 0xBBAA
write_byte(22'h000011, 8'hBB);
if (memory[8] !== 16'hBBAA) begin
$display(
"HIGH BYTE PRESERVE ERROR = 0x%04x expected=0xBBAA",
memory[8]
);
$fatal;
end else begin
$display(
"HIGH BYTE PRESERVE 0x12AA -> 0xBBAA PASS"
);
end
// ========================================================
// Signed INT8 values
// ========================================================
write_byte(22'h000020, -8'sd1);
write_byte(22'h000021, -8'sd128);
write_byte(22'h000022, 8'sd127);
read_byte(22'h000020, -8'sd1);
read_byte(22'h000021, -8'sd128);
read_byte(22'h000022, 8'sd127);
// ========================================================
// Final result
// ========================================================
$display("");
$display("========================================");
$display("INT8 MEMORY ACCESS TEST PASSED");
$display("BYTE ADDRESSING : PASS");
$display("LOW BYTE : PASS");
$display("HIGH BYTE : PASS");
$display("INT8 PACKING : PASS");
$display("BYTE PRESERVATION : PASS");
$display("SIGNED INT8 : PASS");
$display("========================================");
$display("");
$finish;
end
endmodule
File diff suppressed because it is too large Load Diff
+22197
View File
File diff suppressed because one or more lines are too long
+455
View File
@@ -0,0 +1,455 @@
`timescale 1ns/1ps
module tb;
localparam ADDR_WIDTH = 22;
localparam DATA_WIDTH = 16;
localparam CLK_PERIOD = 12.5; // 80 MHz
// ============================================================
// Clock / Reset
// ============================================================
reg clk;
reg rst;
// ============================================================
// INT8 interface
// ============================================================
reg req;
reg wr;
reg [ADDR_WIDTH-1:0] addr;
reg signed [7:0] wdata;
wire signed [7:0] rdata;
wire ready;
// ============================================================
// memory_interface
// ============================================================
wire mem_req;
wire mem_wr;
wire [ADDR_WIDTH-1:0] mem_addr;
wire [DATA_WIDTH-1:0] mem_wdata;
wire mem_lb_n;
wire mem_ub_n;
wire [DATA_WIDTH-1:0] mem_rdata;
wire mem_ready;
// ============================================================
// Memory interface -> PSRAM controller
// ============================================================
wire psram_mem_req;
wire psram_mem_wr;
wire [ADDR_WIDTH-1:0] psram_mem_addr;
wire [DATA_WIDTH-1:0] psram_mem_wdata;
wire psram_mem_lb_n;
wire psram_mem_ub_n;
wire [DATA_WIDTH-1:0] psram_mem_rdata;
wire psram_mem_ready;
// ============================================================
// PSRAM physical interface
// ============================================================
wire [ADDR_WIDTH-1:0] psram_a;
wire [DATA_WIDTH-1:0] psram_dq;
wire psram_ce_n;
wire psram_oe_n;
wire psram_we_n;
wire psram_lb_n;
wire psram_ub_n;
wire psram_zz_n;
// ============================================================
// INT8 MEMORY ACCESS
// ============================================================
int8_memory_access #(
.ADDR_WIDTH(ADDR_WIDTH)
) int8_access (
.clk (clk),
.rst (rst),
.req (req),
.wr (wr),
.addr (addr),
.wdata (wdata),
.rdata (rdata),
.ready (ready),
.mem_req (mem_req),
.mem_wr (mem_wr),
.mem_addr (mem_addr),
.mem_wdata (mem_wdata),
.mem_lb_n (mem_lb_n),
.mem_ub_n (mem_ub_n),
.mem_rdata (mem_rdata),
.mem_ready (mem_ready)
);
// ============================================================
// MEMORY INTERFACE
// ============================================================
memory_interface #(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(DATA_WIDTH)
) memory_if (
.clk (clk),
.rst (rst),
.req (mem_req),
.wr (mem_wr),
.addr (mem_addr),
.wdata (mem_wdata),
.lb_n (mem_lb_n),
.ub_n (mem_ub_n),
.rdata (mem_rdata),
.ready (mem_ready),
.mem_req (psram_mem_req),
.mem_wr (psram_mem_wr),
.mem_addr (psram_mem_addr),
.mem_wdata (psram_mem_wdata),
.mem_lb_n (psram_mem_lb_n),
.mem_ub_n (psram_mem_ub_n),
.mem_rdata (psram_mem_rdata),
.mem_ready (psram_mem_ready)
);
// ============================================================
// PSRAM CONTROLLER
// ============================================================
psram_controller #(
.ADDR_WIDTH (ADDR_WIDTH),
.DATA_WIDTH (DATA_WIDTH),
.CLK_FREQ_MHZ (80)
) psram_ctrl (
.clk (clk),
.rst (rst),
.mem_req (psram_mem_req),
.mem_wr (psram_mem_wr),
.mem_addr (psram_mem_addr),
.mem_wdata (psram_mem_wdata),
.mem_lb_n (psram_mem_lb_n),
.mem_ub_n (psram_mem_ub_n),
.mem_rdata (psram_mem_rdata),
.mem_ready (psram_mem_ready),
.psram_a (psram_a),
.psram_dq (psram_dq),
.psram_ce_n(psram_ce_n),
.psram_oe_n(psram_oe_n),
.psram_we_n(psram_we_n),
.psram_lb_n(psram_lb_n),
.psram_ub_n(psram_ub_n),
.psram_zz_n(psram_zz_n)
);
// ============================================================
// PSRAM MODEL
// ============================================================
psram_model #(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(DATA_WIDTH),
.DEPTH(16384)
) psram (
.clk (clk),
.a (psram_a),
.dq (psram_dq),
.ce_n (psram_ce_n),
.oe_n (psram_oe_n),
.we_n (psram_we_n),
.lb_n (psram_lb_n),
.ub_n (psram_ub_n),
.zz_n (psram_zz_n)
);
// ============================================================
// Clock
// ============================================================
initial begin
clk = 1'b0;
forever #(CLK_PERIOD / 2.0)
clk = ~clk;
end
// ============================================================
// VCD
// ============================================================
initial begin
$dumpfile("sim/int8_psram_integration.vcd");
$dumpvars(0, tb);
end
// ============================================================
// Write INT8
// ============================================================
task write_byte;
input [ADDR_WIDTH-1:0] byte_addr;
input signed [7:0] data;
begin
@(posedge clk);
addr <= byte_addr;
wdata <= data;
wr <= 1'b1;
req <= 1'b1;
@(posedge clk);
req <= 1'b0;
wait (ready);
$display(
"WRITE BYTE addr=0x%08x data=0x%02x PASS",
byte_addr,
data
);
@(posedge clk);
end
endtask
// ============================================================
// Read INT8
// ============================================================
task read_byte;
input [ADDR_WIDTH-1:0] byte_addr;
input signed [7:0] expected;
begin
@(posedge clk);
addr <= byte_addr;
wr <= 1'b0;
req <= 1'b1;
@(posedge clk);
req <= 1'b0;
wait (ready);
if (rdata !== expected) begin
$display(
"READ BYTE addr=0x%08x FAIL got=0x%02x expected=0x%02x",
byte_addr,
rdata,
expected
);
$fatal;
end else begin
$display(
"READ BYTE addr=0x%08x data=0x%02x PASS",
byte_addr,
rdata
);
end
@(posedge clk);
end
endtask
// ============================================================
// Test
// ============================================================
integer i;
integer test_addr;
reg signed [7:0] test_data;
initial begin
req = 1'b0;
wr = 1'b0;
addr = 0;
wdata = 0;
rst = 1'b1;
repeat (5)
@(posedge clk);
rst = 1'b0;
$display("");
$display("========================================");
$display("INT8 + PSRAM FULL INTEGRATION TEST");
$display("========================================");
$display("");
// --------------------------------------------------------
// Wait for PSRAM initialization
// --------------------------------------------------------
wait (psram_ctrl.state == psram_ctrl.STATE_IDLE);
$display("PSRAM initialization complete");
$display("");
// ========================================================
// BASIC INT8 PACKING
// ========================================================
write_byte(22'h000000, 8'h12);
write_byte(22'h000001, 8'h34);
write_byte(22'h000002, 8'h56);
write_byte(22'h000003, 8'h78);
read_byte(22'h000000, 8'h12);
read_byte(22'h000001, 8'h34);
read_byte(22'h000002, 8'h56);
read_byte(22'h000003, 8'h78);
// ========================================================
// BYTE PRESERVATION
// ========================================================
write_byte(22'h000010, 8'h34);
write_byte(22'h000011, 8'h12);
write_byte(22'h000010, 8'hAA);
read_byte(22'h000010, 8'hAA);
read_byte(22'h000011, 8'h12);
write_byte(22'h000011, 8'hBB);
read_byte(22'h000010, 8'hAA);
read_byte(22'h000011, 8'hBB);
// ========================================================
// SIGNED INT8
// ========================================================
write_byte(22'h000020, -8'sd1);
write_byte(22'h000021, -8'sd128);
write_byte(22'h000022, 8'sd127);
read_byte(22'h000020, -8'sd1);
read_byte(22'h000021, -8'sd128);
read_byte(22'h000022, 8'sd127);
// ========================================================
// SPARSE ADDRESSES
// ========================================================
write_byte(22'h000100, 8'h11);
write_byte(22'h000101, 8'h22);
write_byte(22'h001000, 8'h33);
write_byte(22'h001001, 8'h44);
write_byte(22'h003FFE, 8'h55);
write_byte(22'h003FFF, 8'h66);
read_byte(22'h000100, 8'h11);
read_byte(22'h000101, 8'h22);
read_byte(22'h001000, 8'h33);
read_byte(22'h001001, 8'h44);
read_byte(22'h003FFE, 8'h55);
read_byte(22'h003FFF, 8'h66);
// ========================================================
// STRESS TEST
// ========================================================
$display("");
$display("========================================");
$display("INT8 PSRAM STRESS TEST");
$display("2048 WRITE + READ BYTE TRANSACTIONS");
$display("========================================");
$display("");
for (i = 0; i < 2048; i = i + 1) begin
test_addr =
((i * 7919) ^ (i << 5)) & 16'h7FFF;
test_data =
((i * 1237) ^ 8'hA5);
write_byte(
test_addr,
test_data
);
read_byte(
test_addr,
test_data
);
if ((i % 128) == 0)
$display(
"STRESS %0d / 2048 PASS",
i
);
end
// ========================================================
// Final result
// ========================================================
$display("");
$display("========================================");
$display("INT8 + PSRAM INTEGRATION TEST PASSED");
$display("BYTE ADDRESSING : PASS");
$display("LB# / UB# : PASS");
$display("INT8 PACKING : PASS");
$display("BYTE PRESERVATION : PASS");
$display("SIGNED INT8 : PASS");
$display("SPARSE ADDRESSES : PASS");
$display("2048 STRESS : PASS");
$display("========================================");
$display("");
$finish;
end
endmodule
File diff suppressed because it is too large Load Diff
+504
View File
@@ -0,0 +1,504 @@
#! /opt/homebrew/Cellar/icarus-verilog/13.0/bin/vvp
:ivl_version "13.0 (stable)" "(v13_0)";
:ivl_delay_selection "TYPICAL";
:vpi_time_precision - 12;
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/system.vpi";
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/vhdl_sys.vpi";
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/vhdl_textio.vpi";
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/v2005_math.vpi";
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/va_math.vpi";
:vpi_module "/opt/homebrew/Cellar/icarus-verilog/13.0/lib/ivl/v2009.vpi";
S_0x1008dc9a0 .scope package, "$unit" "$unit" 2 1;
.timescale 0 0;
S_0x1008dbb60 .scope module, "memory_interface_tb" "memory_interface_tb" 3 3;
.timescale -9 -12;
P_0x1008dbce0 .param/l "ADDR_WIDTH" 0 3 5, +C4<00000000000000000000000000010110>;
P_0x1008dbd20 .param/l "DATA_WIDTH" 0 3 6, +C4<00000000000000000000000000010000>;
v0x83f094e60_0 .var "addr", 21 0;
v0x83f094f00_0 .var "clk", 0 0;
v0x83f094fa0_0 .net "mem_addr", 21 0, v0x1008cea80_0; 1 drivers
v0x83f095040_0 .net "mem_rdata", 15 0, v0x83f094780_0; 1 drivers
v0x83f0950e0_0 .net "mem_ready", 0 0, v0x83f094820_0; 1 drivers
v0x83f095180_0 .net "mem_req", 0 0, v0x1008d8580_0; 1 drivers
v0x83f095220_0 .net "mem_wdata", 15 0, v0x1008d8620_0; 1 drivers
v0x83f0952c0_0 .net "mem_wr", 0 0, v0x1008d86c0_0; 1 drivers
v0x83f095360_0 .net "rdata", 15 0, v0x1008d8760_0; 1 drivers
v0x83f095400_0 .net "ready", 0 0, v0x1008d8800_0; 1 drivers
v0x83f0954a0_0 .var "req", 0 0;
v0x83f095540_0 .var "rst", 0 0;
v0x83f0955e0_0 .var "wdata", 15 0;
v0x83f095680_0 .var "wr", 0 0;
S_0x1008d97e0 .scope module, "dut" "memory_interface" 3 46, 4 1 0, S_0x1008dbb60;
.timescale -9 -12;
.port_info 0 /INPUT 1 "clk";
.port_info 1 /INPUT 1 "rst";
.port_info 2 /INPUT 1 "req";
.port_info 3 /INPUT 1 "wr";
.port_info 4 /INPUT 22 "addr";
.port_info 5 /INPUT 16 "wdata";
.port_info 6 /OUTPUT 16 "rdata";
.port_info 7 /OUTPUT 1 "ready";
.port_info 8 /OUTPUT 1 "mem_req";
.port_info 9 /OUTPUT 1 "mem_wr";
.port_info 10 /OUTPUT 22 "mem_addr";
.port_info 11 /OUTPUT 16 "mem_wdata";
.port_info 12 /INPUT 16 "mem_rdata";
.port_info 13 /INPUT 1 "mem_ready";
P_0x1008dcb20 .param/l "ADDR_WIDTH" 0 4 2, +C4<00000000000000000000000000010110>;
P_0x1008dcb60 .param/l "DATA_WIDTH" 0 4 3, +C4<00000000000000000000000000010000>;
P_0x1008dcba0 .param/l "STATE_IDLE" 1 4 27, C4<00>;
P_0x1008dcbe0 .param/l "STATE_WAIT" 1 4 28, C4<01>;
v0x1008ce940_0 .net "addr", 21 0, v0x83f094e60_0; 1 drivers
v0x1008ce9e0_0 .net "clk", 0 0, v0x83f094f00_0; 1 drivers
v0x1008cea80_0 .var "mem_addr", 21 0;
v0x1008ceb20_0 .net "mem_rdata", 15 0, v0x83f094780_0; alias, 1 drivers
v0x1008cebc0_0 .net "mem_ready", 0 0, v0x83f094820_0; alias, 1 drivers
v0x1008d8580_0 .var "mem_req", 0 0;
v0x1008d8620_0 .var "mem_wdata", 15 0;
v0x1008d86c0_0 .var "mem_wr", 0 0;
v0x1008d8760_0 .var "rdata", 15 0;
v0x1008d8800_0 .var "ready", 0 0;
v0x1008de100_0 .net "req", 0 0, v0x83f0954a0_0; 1 drivers
v0x1008de1a0_0 .net "rst", 0 0, v0x83f095540_0; 1 drivers
v0x83f094000_0 .var "state", 1 0;
v0x83f0940a0_0 .net "wdata", 15 0, v0x83f0955e0_0; 1 drivers
v0x83f094140_0 .net "wr", 0 0, v0x83f095680_0; 1 drivers
E_0x83ec14080 .event posedge, v0x1008ce9e0_0;
S_0x1008de240 .scope module, "memory" "memory_model" 3 76, 5 1 0, S_0x1008dbb60;
.timescale -9 -12;
.port_info 0 /INPUT 1 "clk";
.port_info 1 /INPUT 1 "rst";
.port_info 2 /INPUT 1 "req";
.port_info 3 /INPUT 1 "wr";
.port_info 4 /INPUT 22 "addr";
.port_info 5 /INPUT 16 "wdata";
.port_info 6 /OUTPUT 16 "rdata";
.port_info 7 /OUTPUT 1 "ready";
P_0x1008de3c0 .param/l "ADDR_WIDTH" 0 5 2, +C4<00000000000000000000000000010110>;
P_0x1008de400 .param/l "DATA_WIDTH" 0 5 3, +C4<00000000000000000000000000010000>;
P_0x1008de440 .param/l "DEPTH" 0 5 4, +C4<00000000000000000001000000000000>;
P_0x1008de480 .param/l "READ_LATENCY" 0 5 5, +C4<00000000000000000000000000000010>;
v0x83f0941e0_0 .net "addr", 21 0, v0x1008cea80_0; alias, 1 drivers
v0x83f094280_0 .var "busy", 0 0;
v0x83f094320_0 .net "clk", 0 0, v0x83f094f00_0; alias, 1 drivers
v0x83f0943c0_0 .var/i "delay_count", 31 0;
v0x83f094460_0 .var/i "i", 31 0;
v0x83f094500 .array "mem", 4095 0, 15 0;
v0x83f0945a0_0 .var "pending_addr", 21 0;
v0x83f094640_0 .var "pending_wdata", 15 0;
v0x83f0946e0_0 .var "pending_wr", 0 0;
v0x83f094780_0 .var "rdata", 15 0;
v0x83f094820_0 .var "ready", 0 0;
v0x83f0948c0_0 .net "req", 0 0, v0x1008d8580_0; alias, 1 drivers
v0x83f094960_0 .net "rst", 0 0, v0x83f095540_0; alias, 1 drivers
v0x83f094a00_0 .net "wdata", 15 0, v0x1008d8620_0; alias, 1 drivers
v0x83f094aa0_0 .net "wr", 0 0, v0x1008d86c0_0; alias, 1 drivers
S_0x1008ded00 .scope task, "read_mem" "read_mem" 3 120, 3 120 0, S_0x1008dbb60;
.timescale -9 -12;
v0x83f094b40_0 .var "address", 21 0;
v0x83f094be0_0 .var "expected", 15 0;
v0x83f094c80_0 .var "received", 15 0;
E_0x83ec17000 .event anyedge, v0x1008d8800_0;
TD_memory_interface_tb.read_mem ;
%wait E_0x83ec14080;
%load/vec4 v0x83f094b40_0;
%assign/vec4 v0x83f094e60_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x83f095680_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x83f0954a0_0, 0;
%wait E_0x83ec14080;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x83f0954a0_0, 0;
T_0.0 ;
%load/vec4 v0x83f095400_0;
%cmpi/ne 1, 0, 1;
%jmp/0xz T_0.1, 6;
%wait E_0x83ec17000;
%jmp T_0.0;
T_0.1 ;
%load/vec4 v0x83f095360_0;
%store/vec4 v0x83f094c80_0, 0, 16;
%wait E_0x83ec14080;
%load/vec4 v0x83f094c80_0;
%load/vec4 v0x83f094be0_0;
%cmp/e;
%jmp/0xz T_0.2, 6;
%vpi_call/w 3 144 "$display", "READ addr=0x%08h data=0x%04h PASS", v0x83f094b40_0, v0x83f094c80_0 {0 0 0};
%jmp T_0.3;
T_0.2 ;
%vpi_call/w 3 150 "$display", "READ addr=0x%08h got=0x%04h expected=0x%04h FAIL", v0x83f094b40_0, v0x83f094c80_0, v0x83f094be0_0 {0 0 0};
%vpi_call/w 3 157 "$fatal" {0 0 0};
T_0.3 ;
%end;
S_0x1008dee80 .scope task, "write_mem" "write_mem" 3 93, 3 93 0, S_0x1008dbb60;
.timescale -9 -12;
v0x83f094d20_0 .var "address", 21 0;
v0x83f094dc0_0 .var "data", 15 0;
TD_memory_interface_tb.write_mem ;
%wait E_0x83ec14080;
%load/vec4 v0x83f094d20_0;
%assign/vec4 v0x83f094e60_0, 0;
%load/vec4 v0x83f094dc0_0;
%assign/vec4 v0x83f0955e0_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x83f095680_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x83f0954a0_0, 0;
%wait E_0x83ec14080;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x83f0954a0_0, 0;
T_1.4 ;
%load/vec4 v0x83f095400_0;
%cmpi/ne 1, 0, 1;
%jmp/0xz T_1.5, 6;
%wait E_0x83ec17000;
%jmp T_1.4;
T_1.5 ;
%wait E_0x83ec14080;
%vpi_call/w 3 112 "$display", "WRITE addr=0x%08h data=0x%04h PASS", v0x83f094d20_0, v0x83f094dc0_0 {0 0 0};
%end;
.scope S_0x1008d97e0;
T_2 ;
%wait E_0x83ec14080;
%load/vec4 v0x1008de1a0_0;
%flag_set/vec4 8;
%jmp/0xz T_2.0, 8;
%pushi/vec4 0, 0, 2;
%assign/vec4 v0x83f094000_0, 0;
%pushi/vec4 0, 0, 16;
%assign/vec4 v0x1008d8760_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x1008d8800_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x1008d8580_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x1008d86c0_0, 0;
%pushi/vec4 0, 0, 22;
%assign/vec4 v0x1008cea80_0, 0;
%pushi/vec4 0, 0, 16;
%assign/vec4 v0x1008d8620_0, 0;
%jmp T_2.1;
T_2.0 ;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x1008d8800_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x1008d8580_0, 0;
%load/vec4 v0x83f094000_0;
%dup/vec4;
%pushi/vec4 0, 0, 2;
%cmp/u;
%jmp/1 T_2.2, 6;
%dup/vec4;
%pushi/vec4 1, 0, 2;
%cmp/u;
%jmp/1 T_2.3, 6;
%pushi/vec4 0, 0, 2;
%assign/vec4 v0x83f094000_0, 0;
%jmp T_2.5;
T_2.2 ;
%load/vec4 v0x1008de100_0;
%flag_set/vec4 8;
%jmp/0xz T_2.6, 8;
%load/vec4 v0x83f094140_0;
%assign/vec4 v0x1008d86c0_0, 0;
%load/vec4 v0x1008ce940_0;
%assign/vec4 v0x1008cea80_0, 0;
%load/vec4 v0x83f0940a0_0;
%assign/vec4 v0x1008d8620_0, 0;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x1008d8580_0, 0;
%pushi/vec4 1, 0, 2;
%assign/vec4 v0x83f094000_0, 0;
T_2.6 ;
%jmp T_2.5;
T_2.3 ;
%load/vec4 v0x1008cebc0_0;
%flag_set/vec4 8;
%jmp/0xz T_2.8, 8;
%load/vec4 v0x1008d86c0_0;
%nor/r;
%flag_set/vec4 8;
%jmp/0xz T_2.10, 8;
%load/vec4 v0x1008ceb20_0;
%assign/vec4 v0x1008d8760_0, 0;
T_2.10 ;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x1008d8800_0, 0;
%pushi/vec4 0, 0, 2;
%assign/vec4 v0x83f094000_0, 0;
T_2.8 ;
%jmp T_2.5;
T_2.5 ;
%pop/vec4 1;
T_2.1 ;
%jmp T_2;
.thread T_2;
.scope S_0x1008de240;
T_3 ;
%wait E_0x83ec14080;
%load/vec4 v0x83f094960_0;
%flag_set/vec4 8;
%jmp/0xz T_3.0, 8;
%pushi/vec4 0, 0, 16;
%assign/vec4 v0x83f094780_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x83f094820_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x83f094280_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x83f0946e0_0, 0;
%pushi/vec4 0, 0, 22;
%assign/vec4 v0x83f0945a0_0, 0;
%pushi/vec4 0, 0, 16;
%assign/vec4 v0x83f094640_0, 0;
%pushi/vec4 0, 0, 32;
%assign/vec4 v0x83f0943c0_0, 0;
%pushi/vec4 0, 0, 32;
%store/vec4 v0x83f094460_0, 0, 32;
T_3.2 ; Top of for-loop
%load/vec4 v0x83f094460_0;
%cmpi/s 4096, 0, 32;
%jmp/0xz T_3.3, 5;
%pushi/vec4 0, 0, 16;
%ix/getv/s 3, v0x83f094460_0;
%ix/load 4, 0, 0; Constant delay
%assign/vec4/a/d v0x83f094500, 0, 4;
T_3.4 ; for-loop step statement
%load/vec4 v0x83f094460_0;
%addi 1, 0, 32;
%store/vec4 v0x83f094460_0, 0, 32;
%jmp T_3.2;
T_3.3 ; for-loop exit label
%jmp T_3.1;
T_3.0 ;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x83f094820_0, 0;
%load/vec4 v0x83f094280_0;
%nor/r;
%flag_set/vec4 8;
%jmp/0xz T_3.5, 8;
%load/vec4 v0x83f0948c0_0;
%flag_set/vec4 8;
%jmp/0xz T_3.7, 8;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x83f094280_0, 0;
%load/vec4 v0x83f094aa0_0;
%assign/vec4 v0x83f0946e0_0, 0;
%load/vec4 v0x83f0941e0_0;
%assign/vec4 v0x83f0945a0_0, 0;
%load/vec4 v0x83f094a00_0;
%assign/vec4 v0x83f094640_0, 0;
%pushi/vec4 2, 0, 32;
%assign/vec4 v0x83f0943c0_0, 0;
T_3.7 ;
%jmp T_3.6;
T_3.5 ;
%load/vec4 v0x83f0943c0_0;
%cmpi/s 0, 0, 32;
%flag_or 5, 4; GT is !LE
%flag_inv 5;
%jmp/0xz T_3.9, 5;
%load/vec4 v0x83f0943c0_0;
%subi 1, 0, 32;
%assign/vec4 v0x83f0943c0_0, 0;
%jmp T_3.10;
T_3.9 ;
%load/vec4 v0x83f0946e0_0;
%flag_set/vec4 8;
%jmp/0xz T_3.11, 8;
%load/vec4 v0x83f0945a0_0;
%pad/u 32;
%cmpi/u 4096, 0, 32;
%jmp/0xz T_3.13, 5;
%load/vec4 v0x83f094640_0;
%ix/getv 3, v0x83f0945a0_0;
%ix/load 4, 0, 0; Constant delay
%assign/vec4/a/d v0x83f094500, 0, 4;
T_3.13 ;
%jmp T_3.12;
T_3.11 ;
%load/vec4 v0x83f0945a0_0;
%pad/u 32;
%cmpi/u 4096, 0, 32;
%jmp/0xz T_3.15, 5;
%ix/getv 4, v0x83f0945a0_0;
%load/vec4a v0x83f094500, 4;
%assign/vec4 v0x83f094780_0, 0;
%jmp T_3.16;
T_3.15 ;
%pushi/vec4 0, 0, 16;
%assign/vec4 v0x83f094780_0, 0;
T_3.16 ;
T_3.12 ;
%pushi/vec4 1, 0, 1;
%assign/vec4 v0x83f094820_0, 0;
%pushi/vec4 0, 0, 1;
%assign/vec4 v0x83f094280_0, 0;
T_3.10 ;
T_3.6 ;
T_3.1 ;
%jmp T_3;
.thread T_3;
.scope S_0x1008dbb60;
T_4 ;
%pushi/vec4 0, 0, 1;
%store/vec4 v0x83f094f00_0, 0, 1;
T_4.0 ;
%delay 5000, 0;
%load/vec4 v0x83f094f00_0;
%inv;
%store/vec4 v0x83f094f00_0, 0, 1;
%jmp T_4.0;
T_4.1 ;
%end;
.thread T_4;
.scope S_0x1008dbb60;
T_5 ;
%pushi/vec4 1, 0, 1;
%store/vec4 v0x83f095540_0, 0, 1;
%pushi/vec4 0, 0, 1;
%store/vec4 v0x83f0954a0_0, 0, 1;
%pushi/vec4 0, 0, 1;
%store/vec4 v0x83f095680_0, 0, 1;
%pushi/vec4 0, 0, 22;
%store/vec4 v0x83f094e60_0, 0, 22;
%pushi/vec4 0, 0, 16;
%store/vec4 v0x83f0955e0_0, 0, 16;
%pushi/vec4 3, 0, 32;
T_5.0 %dup/vec4;
%cmpi/s 0, 0, 32;
%jmp/1xz T_5.1, 5;
%jmp/1 T_5.1, 4;
%subi 1, 0, 32;
%wait E_0x83ec14080;
%jmp T_5.0;
T_5.1 ;
%pop/vec4 1;
%pushi/vec4 0, 0, 1;
%store/vec4 v0x83f095540_0, 0, 1;
%vpi_call/w 3 182 "$display", "\000" {0 0 0};
%vpi_call/w 3 183 "$display", "========================================" {0 0 0};
%vpi_call/w 3 184 "$display", "MEMORY INTERFACE V1 TEST" {0 0 0};
%vpi_call/w 3 185 "$display", "ADDR_WIDTH = %0d", P_0x1008dbce0 {0 0 0};
%vpi_call/w 3 186 "$display", "DATA_WIDTH = %0d", P_0x1008dbd20 {0 0 0};
%vpi_call/w 3 187 "$display", "========================================" {0 0 0};
%vpi_call/w 3 188 "$display", "\000" {0 0 0};
%pushi/vec4 1, 0, 22;
%store/vec4 v0x83f094d20_0, 0, 22;
%pushi/vec4 4660, 0, 16;
%store/vec4 v0x83f094dc0_0, 0, 16;
%fork TD_memory_interface_tb.write_mem, S_0x1008dee80;
%join;
%pushi/vec4 1, 0, 22;
%store/vec4 v0x83f094b40_0, 0, 22;
%pushi/vec4 4660, 0, 16;
%store/vec4 v0x83f094be0_0, 0, 16;
%fork TD_memory_interface_tb.read_mem, S_0x1008ded00;
%join;
%pushi/vec4 16, 0, 22;
%store/vec4 v0x83f094d20_0, 0, 22;
%pushi/vec4 43981, 0, 16;
%store/vec4 v0x83f094dc0_0, 0, 16;
%fork TD_memory_interface_tb.write_mem, S_0x1008dee80;
%join;
%pushi/vec4 16, 0, 22;
%store/vec4 v0x83f094b40_0, 0, 22;
%pushi/vec4 43981, 0, 16;
%store/vec4 v0x83f094be0_0, 0, 16;
%fork TD_memory_interface_tb.read_mem, S_0x1008ded00;
%join;
%pushi/vec4 256, 0, 22;
%store/vec4 v0x83f094d20_0, 0, 22;
%pushi/vec4 21930, 0, 16;
%store/vec4 v0x83f094dc0_0, 0, 16;
%fork TD_memory_interface_tb.write_mem, S_0x1008dee80;
%join;
%pushi/vec4 256, 0, 22;
%store/vec4 v0x83f094b40_0, 0, 22;
%pushi/vec4 21930, 0, 16;
%store/vec4 v0x83f094be0_0, 0, 16;
%fork TD_memory_interface_tb.read_mem, S_0x1008ded00;
%join;
%pushi/vec4 512, 0, 22;
%store/vec4 v0x83f094d20_0, 0, 22;
%pushi/vec4 1, 0, 16;
%store/vec4 v0x83f094dc0_0, 0, 16;
%fork TD_memory_interface_tb.write_mem, S_0x1008dee80;
%join;
%pushi/vec4 513, 0, 22;
%store/vec4 v0x83f094d20_0, 0, 22;
%pushi/vec4 2, 0, 16;
%store/vec4 v0x83f094dc0_0, 0, 16;
%fork TD_memory_interface_tb.write_mem, S_0x1008dee80;
%join;
%pushi/vec4 514, 0, 22;
%store/vec4 v0x83f094d20_0, 0, 22;
%pushi/vec4 3, 0, 16;
%store/vec4 v0x83f094dc0_0, 0, 16;
%fork TD_memory_interface_tb.write_mem, S_0x1008dee80;
%join;
%pushi/vec4 512, 0, 22;
%store/vec4 v0x83f094b40_0, 0, 22;
%pushi/vec4 1, 0, 16;
%store/vec4 v0x83f094be0_0, 0, 16;
%fork TD_memory_interface_tb.read_mem, S_0x1008ded00;
%join;
%pushi/vec4 513, 0, 22;
%store/vec4 v0x83f094b40_0, 0, 22;
%pushi/vec4 2, 0, 16;
%store/vec4 v0x83f094be0_0, 0, 16;
%fork TD_memory_interface_tb.read_mem, S_0x1008ded00;
%join;
%pushi/vec4 514, 0, 22;
%store/vec4 v0x83f094b40_0, 0, 22;
%pushi/vec4 3, 0, 16;
%store/vec4 v0x83f094be0_0, 0, 16;
%fork TD_memory_interface_tb.read_mem, S_0x1008ded00;
%join;
%pushi/vec4 768, 0, 22;
%store/vec4 v0x83f094d20_0, 0, 22;
%pushi/vec4 0, 0, 16;
%store/vec4 v0x83f094dc0_0, 0, 16;
%fork TD_memory_interface_tb.write_mem, S_0x1008dee80;
%join;
%pushi/vec4 768, 0, 22;
%store/vec4 v0x83f094b40_0, 0, 22;
%pushi/vec4 0, 0, 16;
%store/vec4 v0x83f094be0_0, 0, 16;
%fork TD_memory_interface_tb.read_mem, S_0x1008ded00;
%join;
%pushi/vec4 769, 0, 22;
%store/vec4 v0x83f094d20_0, 0, 22;
%pushi/vec4 65535, 0, 16;
%store/vec4 v0x83f094dc0_0, 0, 16;
%fork TD_memory_interface_tb.write_mem, S_0x1008dee80;
%join;
%pushi/vec4 769, 0, 22;
%store/vec4 v0x83f094b40_0, 0, 22;
%pushi/vec4 65535, 0, 16;
%store/vec4 v0x83f094be0_0, 0, 16;
%fork TD_memory_interface_tb.read_mem, S_0x1008ded00;
%join;
%vpi_call/w 3 301 "$display", "\000" {0 0 0};
%vpi_call/w 3 302 "$display", "========================================" {0 0 0};
%vpi_call/w 3 303 "$display", "MEMORY INTERFACE V1 TEST PASSED" {0 0 0};
%vpi_call/w 3 304 "$display", "========================================" {0 0 0};
%vpi_call/w 3 305 "$display", "\000" {0 0 0};
%vpi_call/w 3 307 "$finish" {0 0 0};
%end;
.thread T_5;
.scope S_0x1008dbb60;
T_6 ;
%vpi_call/w 3 315 "$dumpfile", "sim/memory_interface.vcd" {0 0 0};
%vpi_call/w 3 316 "$dumpvars", 32'sb00000000000000000000000000000000, S_0x1008dbb60 {0 0 0};
%end;
.thread T_6;
# The file index is used to find the file name in the following table.
:file_names 6;
"N/A";
"<interactive>";
"-";
"sim/memory_interface_tb.v";
"rtl/memory_interface.v";
"rtl/memory_model.v";
+319
View File
@@ -0,0 +1,319 @@
`timescale 1ns/1ps
module memory_interface_tb;
parameter ADDR_WIDTH = 22;
parameter DATA_WIDTH = 16;
reg clk;
reg rst;
// Host side
reg req;
reg wr;
reg [ADDR_WIDTH-1:0] addr;
reg [DATA_WIDTH-1:0] wdata;
wire [DATA_WIDTH-1:0] rdata;
wire ready;
// Memory side
wire mem_req;
wire mem_wr;
wire [ADDR_WIDTH-1:0] mem_addr;
wire [DATA_WIDTH-1:0] mem_wdata;
wire [DATA_WIDTH-1:0] mem_rdata;
wire mem_ready;
// ------------------------------------------------------------
// Clock
// ------------------------------------------------------------
initial begin
clk = 1'b0;
forever #5 clk = ~clk;
end
// ------------------------------------------------------------
// DUT: Memory Interface
// ------------------------------------------------------------
memory_interface #(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(DATA_WIDTH)
) dut (
.clk(clk),
.rst(rst),
.req(req),
.wr(wr),
.addr(addr),
.wdata(wdata),
.rdata(rdata),
.ready(ready),
.mem_req(mem_req),
.mem_wr(mem_wr),
.mem_addr(mem_addr),
.mem_wdata(mem_wdata),
.mem_rdata(mem_rdata),
.mem_ready(mem_ready)
);
// ------------------------------------------------------------
// Memory model
// ------------------------------------------------------------
memory_model #(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(DATA_WIDTH),
.DEPTH(4096),
.READ_LATENCY(2)
) memory (
.clk(clk),
.rst(rst),
.req(mem_req),
.wr(mem_wr),
.addr(mem_addr),
.wdata(mem_wdata),
.rdata(mem_rdata),
.ready(mem_ready)
);
// ------------------------------------------------------------
// Test utilities
// ------------------------------------------------------------
task write_mem;
input [ADDR_WIDTH-1:0] address;
input [DATA_WIDTH-1:0] data;
begin
@(posedge clk);
addr <= address;
wdata <= data;
wr <= 1'b1;
req <= 1'b1;
@(posedge clk);
req <= 1'b0;
wait (ready);
@(posedge clk);
$display(
"WRITE addr=0x%08h data=0x%04h PASS",
address,
data
);
end
endtask
task read_mem;
input [ADDR_WIDTH-1:0] address;
input [DATA_WIDTH-1:0] expected;
reg [DATA_WIDTH-1:0] received;
begin
@(posedge clk);
addr <= address;
wr <= 1'b0;
req <= 1'b1;
@(posedge clk);
req <= 1'b0;
wait (ready);
received = rdata;
@(posedge clk);
if (received === expected) begin
$display(
"READ addr=0x%08h data=0x%04h PASS",
address,
received
);
end else begin
$display(
"READ addr=0x%08h got=0x%04h expected=0x%04h FAIL",
address,
received,
expected
);
$fatal;
end
end
endtask
// ------------------------------------------------------------
// Test sequence
// ------------------------------------------------------------
initial begin
// Defaults
rst = 1'b1;
req = 1'b0;
wr = 1'b0;
addr = {ADDR_WIDTH{1'b0}};
wdata = {DATA_WIDTH{1'b0}};
// Reset
repeat (3)
@(posedge clk);
rst = 1'b0;
$display("");
$display("========================================");
$display("MEMORY INTERFACE V1 TEST");
$display("ADDR_WIDTH = %0d", ADDR_WIDTH);
$display("DATA_WIDTH = %0d", DATA_WIDTH);
$display("========================================");
$display("");
// --------------------------------------------------------
// Test 1
// --------------------------------------------------------
write_mem(
22'h000001,
16'h1234
);
read_mem(
22'h000001,
16'h1234
);
// --------------------------------------------------------
// Test 2
// --------------------------------------------------------
write_mem(
22'h000010,
16'hABCD
);
read_mem(
22'h000010,
16'hABCD
);
// --------------------------------------------------------
// Test 3
// --------------------------------------------------------
write_mem(
22'h000100,
16'h55AA
);
read_mem(
22'h000100,
16'h55AA
);
// --------------------------------------------------------
// Test 4
// --------------------------------------------------------
// Consecutive different addresses
write_mem(
22'h000200,
16'h0001
);
write_mem(
22'h000201,
16'h0002
);
write_mem(
22'h000202,
16'h0003
);
read_mem(
22'h000200,
16'h0001
);
read_mem(
22'h000201,
16'h0002
);
read_mem(
22'h000202,
16'h0003
);
// --------------------------------------------------------
// Test 5
// --------------------------------------------------------
// Zero value
write_mem(
22'h000300,
16'h0000
);
read_mem(
22'h000300,
16'h0000
);
// --------------------------------------------------------
// Test 6
// --------------------------------------------------------
// Full 16-bit value
write_mem(
22'h000301,
16'hFFFF
);
read_mem(
22'h000301,
16'hFFFF
);
// --------------------------------------------------------
// Finished
// --------------------------------------------------------
$display("");
$display("========================================");
$display("MEMORY INTERFACE V1 TEST PASSED");
$display("========================================");
$display("");
$finish;
end
// ------------------------------------------------------------
// VCD
// ------------------------------------------------------------
initial begin
$dumpfile("sim/memory_interface.vcd");
$dumpvars(0, memory_interface_tb);
end
endmodule
+73405
View File
File diff suppressed because it is too large Load Diff
+23299
View File
File diff suppressed because one or more lines are too long
+625
View File
@@ -0,0 +1,625 @@
`timescale 1ns/1ps
module tb;
localparam ADDR_WIDTH = 22;
localparam DATA_WIDTH = 16;
localparam CLK_PERIOD = 12.5; // 80 MHz
// ============================================================
// CLOCK / RESET
// ============================================================
reg clk;
reg rst;
initial begin
clk = 1'b0;
forever #(CLK_PERIOD / 2.0) clk = ~clk;
end
// ============================================================
// NEURON MEMORY
// ============================================================
reg start;
reg [ADDR_WIDTH-1:0] x_base;
reg [ADDR_WIDTH-1:0] w_base;
reg [ADDR_WIDTH-1:0] bias_addr;
wire signed [7:0] y;
wire busy;
wire done;
// neuron_memory -> memory_interface
wire neuron_mem_req;
wire neuron_mem_wr;
wire [ADDR_WIDTH-1:0] neuron_mem_addr;
wire signed [7:0] neuron_mem_wdata;
wire signed [7:0] neuron_mem_rdata;
wire neuron_mem_ready;
// ============================================================
// TB PRELOAD MASTER
//
// Direct 16-bit master.
// Used only before starting neuron_memory.
// ============================================================
reg tb_mem_req;
reg tb_mem_wr;
reg [ADDR_WIDTH-1:0] tb_mem_addr;
reg [DATA_WIDTH-1:0] tb_mem_wdata;
reg tb_mem_lb_n;
reg tb_mem_ub_n;
wire [DATA_WIDTH-1:0] tb_mem_rdata;
wire tb_mem_ready;
// ============================================================
// SINGLE MASTER MUX
//
// 0 = TB preload master
// 1 = neuron_memory master
// ============================================================
reg use_neuron_master;
wire master_req;
wire master_wr;
wire [ADDR_WIDTH-1:0] master_addr;
wire [DATA_WIDTH-1:0] master_wdata;
wire master_lb_n;
wire master_ub_n;
// ============================================================
// MEMORY INTERFACE
// ============================================================
wire [DATA_WIDTH-1:0] memory_rdata;
wire memory_ready;
wire memory_mem_req;
wire memory_mem_wr;
wire [ADDR_WIDTH-1:0] memory_mem_addr;
wire [DATA_WIDTH-1:0] memory_mem_wdata;
wire memory_mem_lb_n;
wire memory_mem_ub_n;
wire [DATA_WIDTH-1:0] psram_mem_rdata;
wire psram_mem_ready;
assign master_req =
use_neuron_master ? neuron_mem_req : tb_mem_req;
assign master_wr =
use_neuron_master ? neuron_mem_wr : tb_mem_wr;
assign master_addr =
use_neuron_master ? (neuron_mem_addr >> 1) : tb_mem_addr;
assign master_wdata =
use_neuron_master
? (neuron_mem_addr[0]
? {neuron_mem_wdata, 8'h00}
: {8'h00, neuron_mem_wdata})
: tb_mem_wdata;
assign master_lb_n =
use_neuron_master
? (neuron_mem_addr[0] ? 1'b1 : 1'b0)
: tb_mem_lb_n;
assign master_ub_n =
use_neuron_master
? (neuron_mem_addr[0] ? 1'b0 : 1'b1)
: tb_mem_ub_n;
// Return path
assign tb_mem_rdata = memory_rdata;
assign tb_mem_ready = memory_ready;
assign neuron_mem_rdata =
neuron_mem_addr[0]
? memory_rdata[15:8]
: memory_rdata[7:0];
assign neuron_mem_ready = memory_ready;
memory_interface #(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(DATA_WIDTH)
) u_memory_if (
.clk(clk),
.rst(rst),
.req(master_req),
.wr(master_wr),
.addr(master_addr),
.wdata(master_wdata),
.lb_n(master_lb_n),
.ub_n(master_ub_n),
.rdata(memory_rdata),
.ready(memory_ready),
.mem_req(memory_mem_req),
.mem_wr(memory_mem_wr),
.mem_addr(memory_mem_addr),
.mem_wdata(memory_mem_wdata),
.mem_lb_n(memory_mem_lb_n),
.mem_ub_n(memory_mem_ub_n),
.mem_rdata(psram_mem_rdata),
.mem_ready(psram_mem_ready)
);
// ============================================================
// PSRAM PHYSICAL INTERFACE
// ============================================================
wire [ADDR_WIDTH-1:0] psram_a;
wire [DATA_WIDTH-1:0] psram_dq;
wire psram_ce_n;
wire psram_oe_n;
wire psram_we_n;
wire psram_lb_n;
wire psram_ub_n;
wire psram_zz_n;
// ============================================================
// PSRAM CONTROLLER
// ============================================================
psram_controller #(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(DATA_WIDTH),
.CLK_FREQ_MHZ(80)
) u_psram_ctrl (
.clk(clk),
.rst(rst),
.mem_req(memory_mem_req),
.mem_wr(memory_mem_wr),
.mem_addr(memory_mem_addr),
.mem_wdata(memory_mem_wdata),
.mem_lb_n(memory_mem_lb_n),
.mem_ub_n(memory_mem_ub_n),
.mem_rdata(psram_mem_rdata),
.mem_ready(psram_mem_ready),
.psram_a(psram_a),
.psram_dq(psram_dq),
.psram_ce_n(psram_ce_n),
.psram_oe_n(psram_oe_n),
.psram_we_n(psram_we_n),
.psram_lb_n(psram_lb_n),
.psram_ub_n(psram_ub_n),
.psram_zz_n(psram_zz_n)
);
// ============================================================
// PSRAM MODEL
// ============================================================
psram_model #(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(DATA_WIDTH),
.DEPTH(16384)
) u_psram (
.clk(clk),
.a(psram_a),
.dq(psram_dq),
.ce_n(psram_ce_n),
.oe_n(psram_oe_n),
.we_n(psram_we_n),
.lb_n(psram_lb_n),
.ub_n(psram_ub_n),
.zz_n(psram_zz_n)
);
// ============================================================
// NEURON MEMORY
// ============================================================
neuron_memory #(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(8),
.N_INPUTS(32),
.PARALLEL(8),
.ACC_WIDTH(32)
) u_neuron (
.clk(clk),
.rst(rst),
.start(start),
.mem_req(neuron_mem_req),
.mem_wr(neuron_mem_wr),
.mem_addr(neuron_mem_addr),
.mem_wdata(neuron_mem_wdata),
.mem_rdata(neuron_mem_rdata),
.mem_ready(neuron_mem_ready),
.x_base(x_base),
.w_base(w_base),
.bias_addr(bias_addr),
.y(y),
.busy(busy),
.done(done)
);
// ============================================================
// TB WORD WRITE
//
// Directly through:
//
// TB -> memory_interface -> psram_controller -> PSRAM
//
// No force.
// ============================================================
task tb_write_word;
input [ADDR_WIDTH-1:0] addr_i;
input [15:0] data_i;
begin
@(posedge clk);
tb_mem_addr <= addr_i;
tb_mem_wdata <= data_i;
tb_mem_wr <= 1'b1;
tb_mem_lb_n <= 1'b0;
tb_mem_ub_n <= 1'b0;
tb_mem_req <= 1'b1;
@(posedge clk);
tb_mem_req <= 1'b0;
wait (tb_mem_ready);
@(posedge clk);
end
endtask
// ============================================================
// PRELOAD 32 INT8 VALUES
//
// Two INT8 values per PSRAM word.
// ============================================================
task preload_vector;
input [ADDR_WIDTH-1:0] base;
input signed [7:0] value;
integer k;
begin
for (k = 0; k < 32; k = k + 2) begin
tb_write_word(
base + (k >> 1),
{value, value}
);
end
end
endtask
// ============================================================
// PRELOAD WEIGHTS
// ============================================================
task preload_weights;
input [ADDR_WIDTH-1:0] base;
input signed [7:0] value;
integer k;
begin
for (k = 0; k < 32; k = k + 2) begin
tb_write_word(
base + (k >> 1),
{value, value}
);
end
end
endtask
// ============================================================
// PRELOAD BIAS
// ============================================================
task preload_bias;
input [ADDR_WIDTH-1:0] addr_i;
input signed [7:0] value;
begin
// Bias address is a BYTE address.
// Write a full word containing bias in low byte.
tb_write_word(
addr_i >> 1,
{8'h00, value}
);
end
endtask
// ============================================================
// RUN NEURON
// ============================================================
task run_neuron;
input signed [7:0] expected;
input [127:0] test_name;
begin
@(posedge clk);
start <= 1'b1;
@(posedge clk);
start <= 1'b0;
wait (done);
if (y !== expected) begin
$display("");
$display("FAIL %s", test_name);
$display(
" got = %0d (0x%02x)",
y,
y
);
$display(
" expected = %0d (0x%02x)",
expected,
expected
);
$fatal;
end else begin
$display(
"PASS %-16s y=%0d (0x%02x)",
test_name,
y,
y
);
end
@(posedge clk);
end
endtask
// ============================================================
// TEST
// ============================================================
integer i;
initial begin
// --------------------------------------------------------
// Initial values
// --------------------------------------------------------
start = 1'b0;
x_base = 22'h000000;
w_base = 22'h000100;
bias_addr = 22'h000200;
tb_mem_req = 1'b0;
tb_mem_wr = 1'b0;
tb_mem_addr = 0;
tb_mem_wdata = 0;
tb_mem_lb_n = 1'b1;
tb_mem_ub_n = 1'b1;
use_neuron_master = 1'b0;
rst = 1'b1;
// --------------------------------------------------------
// VCD
// --------------------------------------------------------
$dumpfile("sim/neuron_memory.vcd");
$dumpvars(0, tb);
repeat (5)
@(posedge clk);
rst = 1'b0;
// --------------------------------------------------------
// Wait PSRAM initialization
// --------------------------------------------------------
wait (u_psram_ctrl.state == u_psram_ctrl.STATE_IDLE);
$display("");
$display("========================================");
$display("NEURON MEMORY END-TO-END TEST");
$display("========================================");
$display("");
// ========================================================
// PRELOAD PHASE
//
// TB is the ONLY memory master.
// ========================================================
$display("PRELOAD: X = 1");
preload_vector(
x_base,
8'sd1
);
$display("PRELOAD: W = 1");
preload_weights(
w_base,
8'sd1
);
$display("PRELOAD: BIAS = 0");
preload_bias(
bias_addr,
8'sd0
);
// ========================================================
// HAND OVER MEMORY BUS
//
// From this point neuron_memory is the only master.
// ========================================================
use_neuron_master = 1'b1;
$display("");
$display("MEMORY MASTER -> neuron_memory");
$display("");
// ========================================================
// TEST 1
//
// 32 * 1 * 1 + 0 = 32
// ========================================================
run_neuron(
8'sd32,
"SUM=32"
);
// ========================================================
// TEST 2
//
// 32 * 1 * 4 = 128
// Saturated to 127.
//
// We must return control to TB to modify weights.
// ========================================================
use_neuron_master = 1'b0;
preload_weights(
w_base,
8'sd4
);
preload_bias(
bias_addr,
8'sd0
);
use_neuron_master = 1'b1;
run_neuron(
8'sd127,
"SATURATION"
);
// ========================================================
// TEST 3
//
// 32 * 1 * (-1) = -32
// ReLU -> 0
// ========================================================
use_neuron_master = 1'b0;
preload_weights(
w_base,
-8'sd1
);
preload_bias(
bias_addr,
8'sd0
);
use_neuron_master = 1'b1;
run_neuron(
8'sd0,
"RELU"
);
// ========================================================
// TEST 4
//
// 32 * 1 * 1 + 10 = 42
// ========================================================
use_neuron_master = 1'b0;
preload_weights(
w_base,
8'sd1
);
preload_bias(
bias_addr,
8'sd10
);
use_neuron_master = 1'b1;
run_neuron(
8'sd42,
"BIAS=10"
);
// ========================================================
// FINAL
// ========================================================
$display("");
$display("========================================");
$display("NEURON MEMORY TEST PASSED");
$display("========================================");
$display("PSRAM -> INT8 -> NEURON : PASS");
$display("SUM : PASS");
$display("BIAS : PASS");
$display("ReLU : PASS");
$display("SATURATION : PASS");
$display("========================================");
$display("");
$finish;
end
endmodule
+3292 -1609
View File
File diff suppressed because one or more lines are too long
+5430
View File
File diff suppressed because it is too large Load Diff
+4242
View File
File diff suppressed because it is too large Load Diff
+2238
View File
File diff suppressed because it is too large Load Diff
+1801 -1633
View File
File diff suppressed because it is too large Load Diff
+5404
View File
File diff suppressed because it is too large Load Diff
+3350
View File
File diff suppressed because it is too large Load Diff
+5478
View File
File diff suppressed because it is too large Load Diff
+98 -192
View File
@@ -2,12 +2,11 @@
module tb;
parameter DATA_WIDTH = 16;
parameter FRAC_BITS = 8;
parameter N_INPUTS = 32;
parameter DATA_WIDTH = 8;
parameter N_INPUTS = 256;
parameter N_NEURONS = 4;
parameter PARALLEL = 8;
parameter ACC_WIDTH = 40;
parameter PARALLEL = 32;
parameter ACC_WIDTH = 32;
reg clk;
reg rst;
@@ -21,20 +20,17 @@ module tb;
reg signed [DATA_WIDTH*N_NEURONS-1:0]
bias_bus;
wire signed [DATA_WIDTH*N_NEURONS-1:0]
y_bus;
wire signed [DATA_WIDTH*N_NEURONS-1:0] y_bus;
wire busy;
wire done;
integer i;
integer n;
integer errors;
integer expected;
layer #(
.DATA_WIDTH(DATA_WIDTH),
.FRAC_BITS(FRAC_BITS),
.N_INPUTS(N_INPUTS),
.N_NEURONS(N_NEURONS),
.PARALLEL(PARALLEL),
@@ -56,13 +52,6 @@ module tb;
forever #5 clk = ~clk;
end
function signed [15:0] q8_8;
input real value;
begin
q8_8 = $rtoi(value * 256.0);
end
endfunction
task run_layer;
begin
@(posedge clk);
@@ -94,107 +83,81 @@ module tb;
rst = 0;
/*
* All inputs = 1.0
*/
for (i = 0; i < N_INPUTS; i = i + 1)
x_bus[i*DATA_WIDTH +: DATA_WIDTH] = q8_8(1.0);
* Tutti gli input = 1
*/
for (i = 0; i < N_INPUTS; i = i + 1)
x_bus[i*DATA_WIDTH +: DATA_WIDTH] = 8'sd1;
/*
* Neuron 0: weight = 1.0
* result = N_INPUTS
*/
for (i = 0; i < N_INPUTS; i = i + 1)
weights_bus[
0*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = q8_8(1.0);
/*
* N0:
* primi 32 pesi = 1
* restanti 224 = 0
*
* Risultato = 32
*
* Questo verifica che l'accumulatore
* attraversi correttamente tutti gli 8 gruppi.
*/
for (i = 0; i < N_INPUTS; i = i + 1)
weights_bus[
0*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = (i < 32) ? 8'sd1 : 8'sd0;
/*
* Neuron 1: weight = 0.5
* result = N_INPUTS / 2
*/
if (N_NEURONS > 1)
for (i = 0; i < N_INPUTS; i = i + 1)
weights_bus[
1*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = q8_8(0.5);
/*
* N1:
* tutti i 256 pesi = 0
* bias = 10
*
* Risultato = 10
*/
for (i = 0; i < N_INPUTS; i = i + 1)
weights_bus[
1*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = 8'sd0;
/*
* Neuron 2: weight = -0.5
* ReLU -> 0
*/
if (N_NEURONS > 2)
for (i = 0; i < N_INPUTS; i = i + 1)
weights_bus[
2*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = q8_8(-0.5);
bias_bus[1*DATA_WIDTH +: DATA_WIDTH] = 8'sd10;
/*
* Neuron 3: weight = 2.0
* Large positive result -> saturation
*/
if (N_NEURONS > 3)
for (i = 0; i < N_INPUTS; i = i + 1)
weights_bus[
3*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = q8_8(2.0);
/*
* N2:
* tutti i pesi = -1
*
* Risultato = -256
* ReLU -> 0
*/
for (i = 0; i < N_INPUTS; i = i + 1)
weights_bus[
2*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = -8'sd1;
/*
* Neuron 4: zero weights + bias +1
*/
if (N_NEURONS > 4)
bias_bus[4*DATA_WIDTH +: DATA_WIDTH] = q8_8(1.0);
/*
* Neuron 5: zero weights + bias -1
* ReLU -> 0
*/
if (N_NEURONS > 5)
bias_bus[5*DATA_WIDTH +: DATA_WIDTH] = q8_8(-1.0);
/*
* Neuron 6: weight 1.0 + bias -1.0
* result = N_INPUTS - 1
*/
if (N_NEURONS > 6) begin
for (i = 0; i < N_INPUTS; i = i + 1)
weights_bus[
6*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = q8_8(1.0);
bias_bus[6*DATA_WIDTH +: DATA_WIDTH] = q8_8(-1.0);
end
/*
* Neuron 7: weight 0.25 + bias 1
* result = N_INPUTS/4 + 1
*/
if (N_NEURONS > 7) begin
for (i = 0; i < N_INPUTS; i = i + 1)
weights_bus[
7*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = q8_8(0.25);
bias_bus[7*DATA_WIDTH +: DATA_WIDTH] = q8_8(1.0);
end
/*
* N3:
* primi 32 pesi = 4
* restanti = 0
*
* Risultato = 128
* Saturazione INT8 -> 127
*/
for (i = 0; i < N_INPUTS; i = i + 1)
weights_bus[
3*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = (i < 32) ? 8'sd4 : 8'sd0;
$display("");
$display("========================================");
$display("PARAMETRIC LAYER TEST");
$display("INT8 / INT32 PARAMETRIC LAYER TEST");
$display("N_INPUTS = %0d", N_INPUTS);
$display("N_NEURONS = %0d", N_NEURONS);
$display("PARALLEL = %0d", PARALLEL);
$display("DATA_WIDTH = %0d", DATA_WIDTH);
$display("ACC_WIDTH = %0d", ACC_WIDTH);
$display("========================================");
run_layer;
@@ -202,40 +165,45 @@ module tb;
/*
* Neuron 0
*/
expected = N_INPUTS * 256;
expected = 32;
if (y_bus[0*16 +: 16] !== expected[15:0]) begin
if ($signed(y_bus[0*DATA_WIDTH +: DATA_WIDTH]) !== expected) begin
$display("FAIL N0: got %0d expected %0d",
y_bus[0*16 +: 16], expected);
$signed(y_bus[0*DATA_WIDTH +: DATA_WIDTH]),
expected);
errors = errors + 1;
end
else
$display("PASS N0: %0d", y_bus[0*16 +: 16]);
$display("PASS N0: %0d", $signed(y_bus[0*DATA_WIDTH +: DATA_WIDTH]));
/*
* Neuron 1
*/
if (N_NEURONS > 1) begin
expected = (N_INPUTS * 128);
if (y_bus[1*16 +: 16] !== expected[15:0]) begin
expected = 10;
if ($signed(y_bus[1*DATA_WIDTH +: DATA_WIDTH]) !== expected) begin
$display("FAIL N1: got %0d expected %0d",
y_bus[1*16 +: 16], expected);
$signed(y_bus[1*DATA_WIDTH +: DATA_WIDTH]),
expected);
errors = errors + 1;
end
else
$display("PASS N1: %0d", y_bus[1*16 +: 16]);
$display("PASS N1: %0d",
$signed(y_bus[1*DATA_WIDTH +: DATA_WIDTH]));
end
/*
* Neuron 2
*/
if (N_NEURONS > 2) begin
expected = 0;
if (y_bus[2*16 +: 16] !== 16'sd0) begin
if ($signed(y_bus[2*DATA_WIDTH +: DATA_WIDTH]) !== 0) begin
$display("FAIL N2: got %0d expected 0",
y_bus[2*16 +: 16]);
$signed(y_bus[2*DATA_WIDTH +: DATA_WIDTH]));
errors = errors + 1;
end
else
@@ -243,86 +211,24 @@ module tb;
end
/*
* Neuron 3:
* weight = 2.0
* result = N_INPUTS * 2.0
*
* Saturation only occurs when result > 127.996...
*/
* Neuron 3
*
* 32 * 4 = 128
* INT8 positive saturation -> 127
*/
if (N_NEURONS > 3) begin
expected = N_INPUTS * 2 * 256;
expected = 127;
if (expected > 32767)
expected = 32767;
if (y_bus[3*16 +: 16] !== expected[15:0]) begin
$display("FAIL N3: got %0d expected %0d",
y_bus[3*16 +: 16], expected);
errors = errors + 1;
end
else begin
if (expected == 32767)
$display("PASS N3: %0d (saturation)", y_bus[3*16 +: 16]);
else
$display("PASS N3: %0d", y_bus[3*16 +: 16]);
end
end
/*
* Neuron 4
*/
if (N_NEURONS > 4) begin
if (y_bus[4*16 +: 16] !== 16'sd256) begin
$display("FAIL N4: got %0d expected 256",
y_bus[4*16 +: 16]);
if ($signed(y_bus[3*DATA_WIDTH +: DATA_WIDTH]) !== expected) begin
$display("FAIL N3: got %0d expected %0d",
$signed(y_bus[3*DATA_WIDTH +: DATA_WIDTH]),
expected);
errors = errors + 1;
end
else
$display("PASS N4: 256 (bias)");
end
/*
* Neuron 5
*/
if (N_NEURONS > 5) begin
if (y_bus[5*16 +: 16] !== 16'sd0) begin
$display("FAIL N5: got %0d expected 0",
y_bus[5*16 +: 16]);
errors = errors + 1;
end
else
$display("PASS N5: 0 (negative bias + ReLU)");
end
/*
* Neuron 6
*/
if (N_NEURONS > 6) begin
expected = (N_INPUTS - 1) * 256;
if (y_bus[6*16 +: 16] !== expected[15:0]) begin
$display("FAIL N6: got %0d expected %0d",
y_bus[6*16 +: 16], expected);
errors = errors + 1;
end
else
$display("PASS N6: %0d", y_bus[6*16 +: 16]);
end
/*
* Neuron 7
*/
if (N_NEURONS > 7) begin
expected = (N_INPUTS / 4 + 1) * 256;
if (y_bus[7*16 +: 16] !== expected[15:0]) begin
$display("FAIL N7: got %0d expected %0d",
y_bus[7*16 +: 16], expected);
errors = errors + 1;
end
else
$display("PASS N7: %0d", y_bus[7*16 +: 16]);
$display("PASS N3: %0d (saturation)",
$signed(y_bus[3*DATA_WIDTH +: DATA_WIDTH]));
end
if (busy !== 0) begin
@@ -339,9 +245,9 @@ module tb;
$display("========================================");
if (errors == 0)
$display("PARAMETRIC TEST PASSED");
$display("INT8 PARAMETRIC TEST PASSED");
else
$display("PARAMETRIC TEST FAILED: %0d errors", errors);
$display("INT8 PARAMETRIC TEST FAILED: %0d errors", errors);
$display("========================================");
$display("");
File diff suppressed because it is too large Load Diff
+21957
View File
File diff suppressed because one or more lines are too long
+498
View File
@@ -0,0 +1,498 @@
`timescale 1ns/1ps
module tb;
localparam ADDR_WIDTH = 22;
localparam DATA_WIDTH = 16;
localparam CLK_PERIOD = 12.5; // 80 MHz
reg clk;
reg rst;
// ============================================================
// Memory Interface
// ============================================================
reg mem_req;
reg mem_wr;
reg [ADDR_WIDTH-1:0] mem_addr;
reg [DATA_WIDTH-1:0] mem_wdata;
reg mem_lb_n;
reg mem_ub_n;
wire [DATA_WIDTH-1:0] mem_rdata;
wire mem_ready;
// ============================================================
// PSRAM
// ============================================================
wire [ADDR_WIDTH-1:0] psram_a;
wire [DATA_WIDTH-1:0] psram_dq;
wire psram_ce_n;
wire psram_oe_n;
wire psram_we_n;
wire psram_lb_n;
wire psram_ub_n;
wire psram_zz_n;
// ============================================================
// Stress-test variables
// ============================================================
integer stress_i;
integer stress_addr;
reg [15:0] stress_data;
// ============================================================
// DUT
// ============================================================
psram_controller #(
.ADDR_WIDTH (ADDR_WIDTH),
.DATA_WIDTH (DATA_WIDTH),
.CLK_FREQ_MHZ (80)
) dut (
.clk (clk),
.rst (rst),
.mem_req (mem_req),
.mem_wr (mem_wr),
.mem_addr (mem_addr),
.mem_wdata (mem_wdata),
.mem_lb_n (mem_lb_n),
.mem_ub_n (mem_ub_n),
.mem_rdata (mem_rdata),
.mem_ready (mem_ready),
.psram_a (psram_a),
.psram_dq (psram_dq),
.psram_ce_n(psram_ce_n),
.psram_oe_n(psram_oe_n),
.psram_we_n(psram_we_n),
.psram_lb_n(psram_lb_n),
.psram_ub_n(psram_ub_n),
.psram_zz_n(psram_zz_n)
);
// ============================================================
// PSRAM model
// ============================================================
psram_model #(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(DATA_WIDTH),
.DEPTH(16384)
) memory (
.clk (clk),
.a (psram_a),
.dq (psram_dq),
.ce_n (psram_ce_n),
.oe_n (psram_oe_n),
.we_n (psram_we_n),
.lb_n (psram_lb_n),
.ub_n (psram_ub_n),
.zz_n (psram_zz_n)
);
// ============================================================
// Clock
// ============================================================
initial begin
clk = 1'b0;
forever #(CLK_PERIOD / 2.0)
clk = ~clk;
end
// ============================================================
// VCD
// ============================================================
initial begin
$dumpfile("sim/psram_controller.vcd");
$dumpvars(0, tb);
end
// ============================================================
// Helper: write word with byte enables
//
// lb_n = 0 -> low byte enabled
// ub_n = 0 -> high byte enabled
// ============================================================
task write_word;
input [ADDR_WIDTH-1:0] addr;
input [DATA_WIDTH-1:0] data;
input lb;
input ub;
begin
@(posedge clk);
mem_addr <= addr;
mem_wdata <= data;
mem_wr <= 1'b1;
mem_lb_n <= lb;
mem_ub_n <= ub;
mem_req <= 1'b1;
@(posedge clk);
mem_req <= 1'b0;
wait (mem_ready);
$display(
"WRITE addr=0x%08x data=0x%04x LB#=%b UB#=%b PASS",
addr,
data,
lb,
ub
);
@(posedge clk);
end
endtask
// ============================================================
// Helper: read full word
//
// For normal word reads both bytes are enabled.
// ============================================================
task read_word;
input [ADDR_WIDTH-1:0] addr;
input [DATA_WIDTH-1:0] expected;
begin
@(posedge clk);
mem_addr <= addr;
mem_wr <= 1'b0;
mem_lb_n <= 1'b0;
mem_ub_n <= 1'b0;
mem_req <= 1'b1;
@(posedge clk);
mem_req <= 1'b0;
wait (mem_ready);
if (mem_rdata !== expected) begin
$display(
"READ addr=0x%08x FAIL got=0x%04x expected=0x%04x",
addr,
mem_rdata,
expected
);
$fatal;
end else begin
$display(
"READ addr=0x%08x data=0x%04x PASS",
addr,
mem_rdata
);
end
@(posedge clk);
end
endtask
// ============================================================
// Test
// ============================================================
initial begin
mem_req = 1'b0;
mem_wr = 1'b0;
mem_addr = 0;
mem_wdata = 0;
// Both bytes disabled while idle
mem_lb_n = 1'b1;
mem_ub_n = 1'b1;
rst = 1'b1;
repeat (5)
@(posedge clk);
rst = 1'b0;
$display("");
$display("========================================");
$display("PSRAM CONTROLLER V1 TEST");
$display("80 MHz");
$display("4M x 16 PSRAM");
$display("70 ns asynchronous timing");
$display("========================================");
$display("");
wait (dut.state == dut.STATE_IDLE);
$display("PSRAM initialization complete");
$display("");
// ========================================================
// Basic writes / reads
// ========================================================
write_word(22'h000001, 16'h1234, 1'b0, 1'b0);
read_word (22'h000001, 16'h1234);
write_word(22'h000010, 16'hABCD, 1'b0, 1'b0);
read_word (22'h000010, 16'hABCD);
write_word(22'h000100, 16'h55AA, 1'b0, 1'b0);
read_word (22'h000100, 16'h55AA);
// ========================================================
// Consecutive words
// ========================================================
write_word(22'h000200, 16'h0001, 1'b0, 1'b0);
write_word(22'h000201, 16'h0002, 1'b0, 1'b0);
write_word(22'h000202, 16'h0003, 1'b0, 1'b0);
read_word(22'h000200, 16'h0001);
read_word(22'h000201, 16'h0002);
read_word(22'h000202, 16'h0003);
// ========================================================
// Edge values
// ========================================================
write_word(22'h000300, 16'h0000, 1'b0, 1'b0);
read_word (22'h000300, 16'h0000);
write_word(22'h000301, 16'hFFFF, 1'b0, 1'b0);
read_word (22'h000301, 16'hFFFF);
// ========================================================
// High address
// ========================================================
write_word(22'h003FFF, 16'hCAFE, 1'b0, 1'b0);
read_word (22'h003FFF, 16'hCAFE);
// ========================================================
// Basic test passed
// ========================================================
$display("");
$display("========================================");
$display("PSRAM CONTROLLER BASIC TEST PASSED");
$display("========================================");
$display("");
// ========================================================
// BYTE ENABLE TEST
// ========================================================
$display("");
$display("========================================");
$display("PSRAM BYTE ENABLE TEST");
$display("LB# / UB#");
$display("========================================");
$display("");
// --------------------------------------------------------
// Start from known value
// --------------------------------------------------------
write_word(
22'h000400,
16'h1234,
1'b0,
1'b0
);
read_word(
22'h000400,
16'h1234
);
// --------------------------------------------------------
// LOW BYTE ONLY
//
// Initial: 0x1234
// Write: 0x00AA
//
// LB# = 0 -> low byte written
// UB# = 1 -> high byte preserved
//
// Expected: 0x12AA
// --------------------------------------------------------
$display("");
$display("LOW BYTE ONLY");
$display("Initial = 0x1234");
$display("Write = 0x00AA");
$display("LB#=0 UB#=1");
$display("Expected= 0x12AA");
$display("");
write_word(
22'h000400,
16'h00AA,
1'b0,
1'b1
);
read_word(
22'h000400,
16'h12AA
);
// --------------------------------------------------------
// HIGH BYTE ONLY
//
// Current: 0x12AA
// Write: 0xBB00
//
// LB# = 1 -> low byte preserved
// UB# = 0 -> high byte written
//
// Expected: 0xBBAA
// --------------------------------------------------------
$display("");
$display("HIGH BYTE ONLY");
$display("Initial = 0x12AA");
$display("Write = 0xBB00");
$display("LB#=1 UB#=0");
$display("Expected= 0xBBAA");
$display("");
write_word(
22'h000400,
16'hBB00,
1'b1,
1'b0
);
read_word(
22'h000400,
16'hBBAA
);
// --------------------------------------------------------
// FULL WORD
//
// Current: 0xBBAA
// Write: 0xCCDD
//
// LB# = 0 -> low byte written
// UB# = 0 -> high byte written
//
// Expected: 0xCCDD
// --------------------------------------------------------
$display("");
$display("FULL WORD");
$display("Initial = 0xBBAA");
$display("Write = 0xCCDD");
$display("LB#=0 UB#=0");
$display("Expected= 0xCCDD");
$display("");
write_word(
22'h000400,
16'hCCDD,
1'b0,
1'b0
);
read_word(
22'h000400,
16'hCCDD
);
// ========================================================
// BYTE ENABLE TEST PASSED
// ========================================================
$display("");
$display("========================================");
$display("PSRAM BYTE ENABLE TEST PASSED");
$display("LOW BYTE : PASS");
$display("HIGH BYTE : PASS");
$display("FULL WORD : PASS");
$display("PRESERVE : PASS");
$display("========================================");
$display("");
// ========================================================
// STRESS TEST
// ========================================================
$display("");
$display("========================================");
$display("PSRAM STRESS TEST");
$display("2048 WRITE + READ transactions");
$display("========================================");
$display("");
for (stress_i = 0;
stress_i < 2048;
stress_i = stress_i + 1) begin
stress_addr =
((stress_i * 7919) ^ (stress_i << 5)) & 16'h3FFF;
stress_data =
((stress_i * 1237) ^ 16'hA5A5);
write_word(
stress_addr,
stress_data,
1'b0,
1'b0
);
read_word(
stress_addr,
stress_data
);
if ((stress_i % 128) == 0)
$display(
"STRESS %0d / 2048 PASS",
stress_i
);
end
// ========================================================
// Final result
// ========================================================
$display("");
$display("========================================");
$display("PSRAM CONTROLLER V1 TEST PASSED");
$display("2048 WRITE + READ stress transactions");
$display("BYTE ENABLE TEST PASSED");
$display("========================================");
$display("");
$finish;
end
endmodule
+502
View File
@@ -0,0 +1,502 @@
`timescale 1ns/1ps
module psram_model #(
parameter ADDR_WIDTH = 22,
parameter DATA_WIDTH = 16,
parameter DEPTH = 16384
)(
input wire clk,
input wire [ADDR_WIDTH-1:0] a,
inout wire [DATA_WIDTH-1:0] dq,
input wire ce_n,
input wire oe_n,
input wire we_n,
input wire lb_n,
input wire ub_n,
input wire zz_n
);
// ============================================================
// IS66WVE4M16EBLL-70BLI - Rev. D3
// ============================================================
localparam realtime TAA_NS = 70.0;
localparam realtime TRC_NS = 70.0;
localparam realtime TOE_NS = 20.0;
localparam realtime TOH_NS = 5.0;
localparam realtime TLZ_NS = 10.0;
localparam realtime THZ_NS = 8.0;
localparam realtime TWC_NS = 70.0;
localparam realtime TAW_NS = 70.0;
localparam realtime TCW_NS = 70.0;
localparam realtime TWP_NS = 46.0;
localparam realtime TDW_NS = 23.0;
localparam realtime TDH_NS = 0.0;
localparam realtime TWR_NS = 0.0;
localparam realtime TWPH_NS = 10.0;
localparam realtime TPU_NS = 150000.0;
// ============================================================
// Memory
// ============================================================
reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
reg [DATA_WIDTH-1:0] dq_out;
reg dq_oe;
integer i;
assign dq = dq_oe ? dq_out : {DATA_WIDTH{1'bz}};
// ============================================================
// Timing state
// ============================================================
realtime powerup_time;
realtime ce_low_time;
realtime ce_high_time;
realtime oe_low_time;
realtime oe_high_time;
realtime we_low_time;
realtime we_high_time;
realtime last_read_start;
realtime last_write_start;
realtime addr_valid_time;
realtime data_valid_time;
reg read_active;
reg write_active;
reg [ADDR_WIDTH-1:0] active_addr;
reg [DATA_WIDTH-1:0] active_wdata;
// ============================================================
// Initialization
// ============================================================
initial begin
for (i = 0; i < DEPTH; i = i + 1)
mem[i] = 16'h0000;
dq_out = 16'h0000;
dq_oe = 1'b0;
powerup_time = $realtime;
ce_low_time = 0.0;
ce_high_time = 0.0;
oe_low_time = 0.0;
oe_high_time = 0.0;
we_low_time = 0.0;
we_high_time = 0.0;
last_read_start = -1.0;
last_write_start = -1.0;
addr_valid_time = 0.0;
data_valid_time = 0.0;
read_active = 1'b0;
write_active = 1'b0;
active_addr = 0;
active_wdata = 0;
end
// ============================================================
// FUNCTIONAL READ MODEL
// ============================================================
always @(*) begin
dq_oe = 1'b0;
dq_out = 16'h0000;
if (zz_n &&
!ce_n &&
!oe_n &&
we_n) begin
if (a < DEPTH) begin
dq_oe = 1'b1;
if (!lb_n && !ub_n)
dq_out = mem[a];
else if (!lb_n)
dq_out = {
8'h00,
mem[a][7:0]
};
else if (!ub_n)
dq_out = {
mem[a][15:8],
8'h00
};
end
end
end
// ============================================================
// CE# FALLING
// ============================================================
always @(negedge ce_n) begin
if (!zz_n) begin
$display("ERROR: CE# LOW while ZZ# LOW");
$fatal;
end
ce_low_time = $realtime;
addr_valid_time = $realtime;
active_addr = a;
// --------------------------------------------------------
// READ START
// --------------------------------------------------------
if (!oe_n && we_n) begin
if (($realtime - powerup_time) < TPU_NS) begin
$display("ERROR: READ before tPU");
$fatal;
end
if (last_read_start >= 0.0) begin
if (($realtime - last_read_start) < TRC_NS) begin
$display("");
$display("========================================");
$display("PSRAM TIMING ERROR");
$display("tRC violation");
$display("required = %0.2f ns", TRC_NS);
$display("actual = %0.2f ns",
$realtime - last_read_start);
$display("========================================");
$fatal;
end
end
last_read_start = $realtime;
read_active = 1'b1;
end
// --------------------------------------------------------
// WRITE START
// --------------------------------------------------------
if (oe_n && !we_n) begin
if (($realtime - powerup_time) < TPU_NS) begin
$display("ERROR: WRITE before tPU");
$fatal;
end
if (last_write_start >= 0.0) begin
if (($realtime - last_write_start) < TWC_NS) begin
$display("");
$display("========================================");
$display("PSRAM TIMING ERROR");
$display("tWC violation");
$display("required = %0.2f ns", TWC_NS);
$display("actual = %0.2f ns",
$realtime - last_write_start);
$display("========================================");
$fatal;
end
end
last_write_start = $realtime;
write_active = 1'b1;
end
end
// ============================================================
// CE# RISING
// ============================================================
always @(posedge ce_n) begin
realtime access_time;
ce_high_time = $realtime;
access_time = $realtime - ce_low_time;
// --------------------------------------------------------
// READ END
// --------------------------------------------------------
if (read_active) begin
// tAA
if (access_time < TAA_NS) begin
$display("");
$display("========================================");
$display("PSRAM TIMING ERROR");
$display("tAA violation");
$display("required = %0.2f ns", TAA_NS);
$display("actual = %0.2f ns", access_time);
$display("========================================");
$fatal;
end
// tOE
if (($realtime - oe_low_time) < TOE_NS) begin
$display("");
$display("========================================");
$display("PSRAM TIMING ERROR");
$display("tOE violation");
$display("required = %0.2f ns", TOE_NS);
$display("actual = %0.2f ns",
$realtime - oe_low_time);
$display("========================================");
$fatal;
end
read_active = 1'b0;
end
end
// ============================================================
// OE# FALLING
// ============================================================
always @(negedge oe_n) begin
if (!zz_n) begin
$display("ERROR: OE# LOW while ZZ# LOW");
$fatal;
end
if (!ce_n && we_n)
oe_low_time = $realtime;
// Illegal combination
if (!ce_n && !we_n) begin
$display("");
$display("========================================");
$display("PSRAM PROTOCOL ERROR");
$display("OE# and WE# LOW simultaneously");
$display("========================================");
$fatal;
end
end
// ============================================================
// OE# RISING
// ============================================================
always @(posedge oe_n) begin
oe_high_time = $realtime;
// Output must remain valid long enough for tOH
// after address changes. This is checked by the
// controller access window rather than by forcing
// an artificial delay into the functional model.
end
// ============================================================
// WE# FALLING
// ============================================================
always @(negedge we_n) begin
if (!zz_n) begin
$display("ERROR: WE# LOW while ZZ# LOW");
$fatal;
end
if (!ce_n && oe_n) begin
we_low_time = $realtime;
active_addr = a;
active_wdata = dq;
write_active = 1'b1;
end
// Illegal combination
if (!ce_n && !oe_n) begin
$display("");
$display("========================================");
$display("PSRAM PROTOCOL ERROR");
$display("OE# and WE# LOW simultaneously");
$display("========================================");
$fatal;
end
end
// ============================================================
// WE# RISING
// ============================================================
always @(posedge we_n) begin
realtime write_width;
realtime data_setup;
we_high_time = $realtime;
if (write_active) begin
write_width = $realtime - we_low_time;
// ----------------------------------------------------
// tWP
// ----------------------------------------------------
if (write_width < TWP_NS) begin
$display("");
$display("========================================");
$display("PSRAM TIMING ERROR");
$display("tWP violation");
$display("required = %0.2f ns", TWP_NS);
$display("actual = %0.2f ns", write_width);
$display("========================================");
$fatal;
end
// ----------------------------------------------------
// tAW
// ----------------------------------------------------
if (($realtime - addr_valid_time) < TAW_NS) begin
$display("");
$display("========================================");
$display("PSRAM TIMING ERROR");
$display("tAW violation");
$display("required = %0.2f ns", TAW_NS);
$display("actual = %0.2f ns",
$realtime - addr_valid_time);
$display("========================================");
$fatal;
end
// ----------------------------------------------------
// tCW
// ----------------------------------------------------
if (($realtime - ce_low_time) < TCW_NS) begin
$display("");
$display("========================================");
$display("PSRAM TIMING ERROR");
$display("tCW violation");
$display("required = %0.2f ns", TCW_NS);
$display("actual = %0.2f ns",
$realtime - ce_low_time);
$display("========================================");
$fatal;
end
// ----------------------------------------------------
// tDW
//
// Data must be valid before WE# rises.
// Our controller drives DQ from WE# falling,
// therefore setup is much larger than 23 ns.
// ----------------------------------------------------
data_setup = $realtime - we_low_time;
if (data_setup < TDW_NS) begin
$display("");
$display("========================================");
$display("PSRAM TIMING ERROR");
$display("tDW violation");
$display("required = %0.2f ns", TDW_NS);
$display("actual = %0.2f ns", data_setup);
$display("========================================");
$fatal;
end
// ----------------------------------------------------
// tDH = 0 ns
// ----------------------------------------------------
// No additional hold time is required.
// ----------------------------------------------------
// Store data
// ----------------------------------------------------
if (a !== active_addr) begin
$display("");
$display("========================================");
$display("PSRAM PROTOCOL ERROR");
$display("ADDRESS CHANGED DURING WRITE");
$display("========================================");
$fatal;
end
if (a < DEPTH) begin
if (!lb_n)
mem[a][7:0] <= dq[7:0];
if (!ub_n)
mem[a][15:8] <= dq[15:8];
end
write_active = 1'b0;
end
end
// ============================================================
// ZZ#
// ============================================================
always @(negedge zz_n) begin
if (!ce_n) begin
$display("");
$display("========================================");
$display("PSRAM PROTOCOL ERROR");
$display("ZZ# LOW while CE# LOW");
$display("========================================");
$fatal;
end
end
endmodule
File diff suppressed because one or more lines are too long
+10863
View File
File diff suppressed because it is too large Load Diff
+44888
View File
File diff suppressed because one or more lines are too long
View File
+78
View File
@@ -0,0 +1,78 @@
module top (
input clk,
input rst,
input start,
output signed [31:0] y_bus,
output busy,
output done
);
localparam DATA_WIDTH = 8;
localparam N_INPUTS = 256;
localparam N_NEURONS = 4;
localparam PARALLEL = 2;
localparam ACC_WIDTH = 32;
reg signed [DATA_WIDTH*N_INPUTS-1:0] x_bus;
reg signed [DATA_WIDTH*N_INPUTS*N_NEURONS-1:0] weights_bus;
reg signed [DATA_WIDTH*N_NEURONS-1:0] bias_bus;
integer i;
initial begin
x_bus = 0;
weights_bus = 0;
bias_bus = 0;
// x[i] = 1
for (i = 0; i < N_INPUTS; i = i + 1)
x_bus[i*DATA_WIDTH +: DATA_WIDTH] = 8'sd1;
// Neurone 0: primi 32 pesi = +1
for (i = 0; i < 32; i = i + 1)
weights_bus[
0*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = 8'sd1;
// Neurone 1: tutti i pesi = 0, bias = 10
bias_bus[1*DATA_WIDTH +: DATA_WIDTH] = 8'sd10;
// Neurone 2: tutti i pesi = -1
for (i = 0; i < N_INPUTS; i = i + 1)
weights_bus[
2*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = -8'sd1;
// Neurone 3: primi 32 pesi = +4
for (i = 0; i < 32; i = i + 1)
weights_bus[
3*N_INPUTS*DATA_WIDTH +
i*DATA_WIDTH +:
DATA_WIDTH
] = 8'sd4;
end
layer #(
.DATA_WIDTH(DATA_WIDTH),
.N_INPUTS(N_INPUTS),
.N_NEURONS(N_NEURONS),
.PARALLEL(PARALLEL),
.ACC_WIDTH(ACC_WIDTH)
) u_layer (
.clk(clk),
.rst(rst),
.start(start),
.x_bus(x_bus),
.weights_bus(weights_bus),
.bias_bus(bias_bus),
.y_bus(y_bus),
.busy(busy),
.done(done)
);
endmodule