exp: N=16 timing closure fixed (EXP-0056), weight-reuse gives real 7.16x memory speedup without DDR3 (EXP-0057)

EXP-0056: N_SLOTS=16 failed timing on LFE5U-85F (23-24MHz vs 64MHz
target). First hypothesis (dependency_manager.v's serial ready-scan)
was wrong but real -- built and verified priority_encoder_lsb.v (a
generic recursive tree encoder) and dependency_manager_fast.v, bit-
exact equivalent to the original, but integrated it made no real
difference (24.26MHz). The real cause, found from nextpnr's own
critical-path report: nms_activation_fill_ctrl_v3.v's balanced max-
tree was only ever extended to N_SLOTS in {1,2,4,8}, silently falling
back to the original slow scan for 16. Added the missing case
(nms_activation_fill_ctrl_v3_n16.v), verified isolated (10017/10017)
and functionally (D-Stress N=16 still 256/256 bit-exact). Real result:
71.01MHz, PASS at 64MHz (single seed so far).

EXP-0057: built layer_weight_buffer.v, a double-buffered per-layer
weight scratchpad (fill one buffer in the background from SDRAM while
compute reads many times from the other -- weight-stationary reuse,
as opposed to D-Stress's own deliberately zero-reuse pattern). Wired
to the real sdram_controller_openrow.v + sdram_model.v, no new
hardware. For the same 32768 bytes of useful data: zero-reuse costs
27048 real cycles, reuse costs 3777 -- 7.16x real measured speedup on
the SAME SDR SDRAM, no DDR3, no clock change. This is the answer to
whether DDR3 is necessary for a workload class that actually has
reuse (e.g. conv-style face recognition, unlike D-Stress) -- it isn't,
at least not for this reason.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
2026-09-16 12:01:11 +02:00
co-authored by Claude Sonnet 5
parent fce8ff2d66
commit 1ce78dff6e
25 changed files with 25848 additions and 0 deletions
@@ -0,0 +1,15 @@
device: "LFE5U-85F-8BG381C"
cpu: "None"
memtype: "DDR3"
sdram_phy: "ECP5DDRPHY"
sdram_module: "MT41K256M16"
sdram_module_nb: 2
sdram_rank_nb: 1
input_clk_freq: 16e6
sys_clk_freq: 75e6
init_clk_freq: 25e6
cmd_latency: 0
speedgrade: -8
user_ports:
native_0:
type: "native"
@@ -0,0 +1,279 @@
//--------------------------------------------------------------------------------
// Auto-generated by LiteX (9ad3ecf74) on 2026-09-16 08:16:18
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
// CSR Includes.
//--------------------------------------------------------------------------------
#include <generated/soc.h>
#ifndef __GENERATED_CSR_H
#define __GENERATED_CSR_H
#include <stdint.h>
#include <system.h>
#ifndef CSR_ACCESSORS_DEFINED
#include <hw/common.h>
#endif /* ! CSR_ACCESSORS_DEFINED */
#ifndef CSR_BASE
#define CSR_BASE 0x0L
#endif /* ! CSR_BASE */
//--------------------------------------------------------------------------------
// CSR Registers/Fields Definition.
//--------------------------------------------------------------------------------
/* DDRCTRL Registers */
#define CSR_DDRCTRL_BASE (CSR_BASE + 0x0L)
#define CSR_DDRCTRL_INIT_DONE_ADDR (CSR_BASE + 0x0L)
#define CSR_DDRCTRL_INIT_DONE_SIZE 1
#define CSR_DDRCTRL_INIT_ERROR_ADDR (CSR_BASE + 0x4L)
#define CSR_DDRCTRL_INIT_ERROR_SIZE 1
/* DDRCTRL Fields */
/* DDRPHY Registers */
#define CSR_DDRPHY_BASE (CSR_BASE + 0x800L)
#define CSR_DDRPHY_DLY_SEL_ADDR (CSR_BASE + 0x800L)
#define CSR_DDRPHY_DLY_SEL_SIZE 1
#define CSR_DDRPHY_RDLY_DQ_RST_ADDR (CSR_BASE + 0x804L)
#define CSR_DDRPHY_RDLY_DQ_RST_SIZE 1
#define CSR_DDRPHY_RDLY_DQ_INC_ADDR (CSR_BASE + 0x808L)
#define CSR_DDRPHY_RDLY_DQ_INC_SIZE 1
#define CSR_DDRPHY_RDLY_DQ_BITSLIP_RST_ADDR (CSR_BASE + 0x80cL)
#define CSR_DDRPHY_RDLY_DQ_BITSLIP_RST_SIZE 1
#define CSR_DDRPHY_RDLY_DQ_BITSLIP_ADDR (CSR_BASE + 0x810L)
#define CSR_DDRPHY_RDLY_DQ_BITSLIP_SIZE 1
#define CSR_DDRPHY_BURSTDET_CLR_ADDR (CSR_BASE + 0x814L)
#define CSR_DDRPHY_BURSTDET_CLR_SIZE 1
#define CSR_DDRPHY_BURSTDET_SEEN_ADDR (CSR_BASE + 0x818L)
#define CSR_DDRPHY_BURSTDET_SEEN_SIZE 1
/* DDRPHY Fields */
/* SDRAM Registers */
#define CSR_SDRAM_BASE (CSR_BASE + 0x1000L)
#define CSR_SDRAM_DFII_CONTROL_ADDR (CSR_BASE + 0x1000L)
#define CSR_SDRAM_DFII_CONTROL_SIZE 1
#define CSR_SDRAM_DFII_PI0_COMMAND_ADDR (CSR_BASE + 0x1004L)
#define CSR_SDRAM_DFII_PI0_COMMAND_SIZE 1
#define CSR_SDRAM_DFII_PI0_COMMAND_ISSUE_ADDR (CSR_BASE + 0x1008L)
#define CSR_SDRAM_DFII_PI0_COMMAND_ISSUE_SIZE 1
#define CSR_SDRAM_DFII_PI0_ADDRESS_ADDR (CSR_BASE + 0x100cL)
#define CSR_SDRAM_DFII_PI0_ADDRESS_SIZE 1
#define CSR_SDRAM_DFII_PI0_BADDRESS_ADDR (CSR_BASE + 0x1010L)
#define CSR_SDRAM_DFII_PI0_BADDRESS_SIZE 1
#define CSR_SDRAM_DFII_PI0_WRDATA_ADDR (CSR_BASE + 0x1014L)
#define CSR_SDRAM_DFII_PI0_WRDATA_SIZE 2
#define CSR_SDRAM_DFII_PI0_RDDATA_ADDR (CSR_BASE + 0x101cL)
#define CSR_SDRAM_DFII_PI0_RDDATA_SIZE 2
#define CSR_SDRAM_DFII_PI1_COMMAND_ADDR (CSR_BASE + 0x1024L)
#define CSR_SDRAM_DFII_PI1_COMMAND_SIZE 1
#define CSR_SDRAM_DFII_PI1_COMMAND_ISSUE_ADDR (CSR_BASE + 0x1028L)
#define CSR_SDRAM_DFII_PI1_COMMAND_ISSUE_SIZE 1
#define CSR_SDRAM_DFII_PI1_ADDRESS_ADDR (CSR_BASE + 0x102cL)
#define CSR_SDRAM_DFII_PI1_ADDRESS_SIZE 1
#define CSR_SDRAM_DFII_PI1_BADDRESS_ADDR (CSR_BASE + 0x1030L)
#define CSR_SDRAM_DFII_PI1_BADDRESS_SIZE 1
#define CSR_SDRAM_DFII_PI1_WRDATA_ADDR (CSR_BASE + 0x1034L)
#define CSR_SDRAM_DFII_PI1_WRDATA_SIZE 2
#define CSR_SDRAM_DFII_PI1_RDDATA_ADDR (CSR_BASE + 0x103cL)
#define CSR_SDRAM_DFII_PI1_RDDATA_SIZE 2
/* SDRAM Fields */
#define CSR_SDRAM_DFII_CONTROL_SEL_OFFSET 0
#define CSR_SDRAM_DFII_CONTROL_SEL_SIZE 1
#define CSR_SDRAM_DFII_CONTROL_CKE_OFFSET 1
#define CSR_SDRAM_DFII_CONTROL_CKE_SIZE 1
#define CSR_SDRAM_DFII_CONTROL_ODT_OFFSET 2
#define CSR_SDRAM_DFII_CONTROL_ODT_SIZE 1
#define CSR_SDRAM_DFII_CONTROL_RESET_N_OFFSET 3
#define CSR_SDRAM_DFII_CONTROL_RESET_N_SIZE 1
#define CSR_SDRAM_DFII_PI0_COMMAND_CS_OFFSET 0
#define CSR_SDRAM_DFII_PI0_COMMAND_CS_SIZE 1
#define CSR_SDRAM_DFII_PI0_COMMAND_WE_OFFSET 1
#define CSR_SDRAM_DFII_PI0_COMMAND_WE_SIZE 1
#define CSR_SDRAM_DFII_PI0_COMMAND_CAS_OFFSET 2
#define CSR_SDRAM_DFII_PI0_COMMAND_CAS_SIZE 1
#define CSR_SDRAM_DFII_PI0_COMMAND_RAS_OFFSET 3
#define CSR_SDRAM_DFII_PI0_COMMAND_RAS_SIZE 1
#define CSR_SDRAM_DFII_PI0_COMMAND_WREN_OFFSET 4
#define CSR_SDRAM_DFII_PI0_COMMAND_WREN_SIZE 1
#define CSR_SDRAM_DFII_PI0_COMMAND_RDEN_OFFSET 5
#define CSR_SDRAM_DFII_PI0_COMMAND_RDEN_SIZE 1
#define CSR_SDRAM_DFII_PI0_COMMAND_CS_TOP_OFFSET 6
#define CSR_SDRAM_DFII_PI0_COMMAND_CS_TOP_SIZE 1
#define CSR_SDRAM_DFII_PI0_COMMAND_CS_BOTTOM_OFFSET 7
#define CSR_SDRAM_DFII_PI0_COMMAND_CS_BOTTOM_SIZE 1
#define CSR_SDRAM_DFII_PI1_COMMAND_CS_OFFSET 0
#define CSR_SDRAM_DFII_PI1_COMMAND_CS_SIZE 1
#define CSR_SDRAM_DFII_PI1_COMMAND_WE_OFFSET 1
#define CSR_SDRAM_DFII_PI1_COMMAND_WE_SIZE 1
#define CSR_SDRAM_DFII_PI1_COMMAND_CAS_OFFSET 2
#define CSR_SDRAM_DFII_PI1_COMMAND_CAS_SIZE 1
#define CSR_SDRAM_DFII_PI1_COMMAND_RAS_OFFSET 3
#define CSR_SDRAM_DFII_PI1_COMMAND_RAS_SIZE 1
#define CSR_SDRAM_DFII_PI1_COMMAND_WREN_OFFSET 4
#define CSR_SDRAM_DFII_PI1_COMMAND_WREN_SIZE 1
#define CSR_SDRAM_DFII_PI1_COMMAND_RDEN_OFFSET 5
#define CSR_SDRAM_DFII_PI1_COMMAND_RDEN_SIZE 1
#define CSR_SDRAM_DFII_PI1_COMMAND_CS_TOP_OFFSET 6
#define CSR_SDRAM_DFII_PI1_COMMAND_CS_TOP_SIZE 1
#define CSR_SDRAM_DFII_PI1_COMMAND_CS_BOTTOM_OFFSET 7
#define CSR_SDRAM_DFII_PI1_COMMAND_CS_BOTTOM_SIZE 1
//--------------------------------------------------------------------------------
// CSR Registers Access Functions.
//--------------------------------------------------------------------------------
#ifndef LITEX_CSR_ACCESS_FUNCTIONS
#define LITEX_CSR_ACCESS_FUNCTIONS 1
#endif
#if LITEX_CSR_ACCESS_FUNCTIONS
/* DDRCTRL Access Functions */
static inline uint32_t ddrctrl_init_done_read(void) {
return csr_read_simple((CSR_BASE + 0x0L));
}
static inline void ddrctrl_init_done_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x0L));
}
static inline uint32_t ddrctrl_init_error_read(void) {
return csr_read_simple((CSR_BASE + 0x4L));
}
static inline void ddrctrl_init_error_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x4L));
}
/* DDRPHY Access Functions */
static inline uint32_t ddrphy_dly_sel_read(void) {
return csr_read_simple((CSR_BASE + 0x800L));
}
static inline void ddrphy_dly_sel_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x800L));
}
static inline uint32_t ddrphy_rdly_dq_rst_read(void) {
return csr_read_simple((CSR_BASE + 0x804L));
}
static inline void ddrphy_rdly_dq_rst_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x804L));
}
static inline uint32_t ddrphy_rdly_dq_inc_read(void) {
return csr_read_simple((CSR_BASE + 0x808L));
}
static inline void ddrphy_rdly_dq_inc_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x808L));
}
static inline uint32_t ddrphy_rdly_dq_bitslip_rst_read(void) {
return csr_read_simple((CSR_BASE + 0x80cL));
}
static inline void ddrphy_rdly_dq_bitslip_rst_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x80cL));
}
static inline uint32_t ddrphy_rdly_dq_bitslip_read(void) {
return csr_read_simple((CSR_BASE + 0x810L));
}
static inline void ddrphy_rdly_dq_bitslip_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x810L));
}
static inline uint32_t ddrphy_burstdet_clr_read(void) {
return csr_read_simple((CSR_BASE + 0x814L));
}
static inline void ddrphy_burstdet_clr_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x814L));
}
static inline uint32_t ddrphy_burstdet_seen_read(void) {
return csr_read_simple((CSR_BASE + 0x818L));
}
/* SDRAM Access Functions */
static inline uint32_t sdram_dfii_control_read(void) {
return csr_read_simple((CSR_BASE + 0x1000L));
}
static inline void sdram_dfii_control_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x1000L));
}
static inline uint32_t sdram_dfii_pi0_command_read(void) {
return csr_read_simple((CSR_BASE + 0x1004L));
}
static inline void sdram_dfii_pi0_command_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x1004L));
}
static inline uint32_t sdram_dfii_pi0_command_issue_read(void) {
return csr_read_simple((CSR_BASE + 0x1008L));
}
static inline void sdram_dfii_pi0_command_issue_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x1008L));
}
static inline uint32_t sdram_dfii_pi0_address_read(void) {
return csr_read_simple((CSR_BASE + 0x100cL));
}
static inline void sdram_dfii_pi0_address_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x100cL));
}
static inline uint32_t sdram_dfii_pi0_baddress_read(void) {
return csr_read_simple((CSR_BASE + 0x1010L));
}
static inline void sdram_dfii_pi0_baddress_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x1010L));
}
static inline uint64_t sdram_dfii_pi0_wrdata_read(void) {
uint64_t r = csr_read_simple((CSR_BASE + 0x1014L));
r <<= 32;
r |= csr_read_simple((CSR_BASE + 0x1018L));
return r;
}
static inline void sdram_dfii_pi0_wrdata_write(uint64_t v) {
csr_write_simple(v >> 32, (CSR_BASE + 0x1014L));
csr_write_simple(v, (CSR_BASE + 0x1018L));
}
static inline uint64_t sdram_dfii_pi0_rddata_read(void) {
uint64_t r = csr_read_simple((CSR_BASE + 0x101cL));
r <<= 32;
r |= csr_read_simple((CSR_BASE + 0x1020L));
return r;
}
static inline uint32_t sdram_dfii_pi1_command_read(void) {
return csr_read_simple((CSR_BASE + 0x1024L));
}
static inline void sdram_dfii_pi1_command_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x1024L));
}
static inline uint32_t sdram_dfii_pi1_command_issue_read(void) {
return csr_read_simple((CSR_BASE + 0x1028L));
}
static inline void sdram_dfii_pi1_command_issue_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x1028L));
}
static inline uint32_t sdram_dfii_pi1_address_read(void) {
return csr_read_simple((CSR_BASE + 0x102cL));
}
static inline void sdram_dfii_pi1_address_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x102cL));
}
static inline uint32_t sdram_dfii_pi1_baddress_read(void) {
return csr_read_simple((CSR_BASE + 0x1030L));
}
static inline void sdram_dfii_pi1_baddress_write(uint32_t v) {
csr_write_simple(v, (CSR_BASE + 0x1030L));
}
static inline uint64_t sdram_dfii_pi1_wrdata_read(void) {
uint64_t r = csr_read_simple((CSR_BASE + 0x1034L));
r <<= 32;
r |= csr_read_simple((CSR_BASE + 0x1038L));
return r;
}
static inline void sdram_dfii_pi1_wrdata_write(uint64_t v) {
csr_write_simple(v >> 32, (CSR_BASE + 0x1034L));
csr_write_simple(v, (CSR_BASE + 0x1038L));
}
static inline uint64_t sdram_dfii_pi1_rddata_read(void) {
uint64_t r = csr_read_simple((CSR_BASE + 0x103cL));
r <<= 32;
r |= csr_read_simple((CSR_BASE + 0x1040L));
return r;
}
#endif /* LITEX_CSR_ACCESS_FUNCTIONS */
#endif /* ! __GENERATED_CSR_H */
@@ -0,0 +1,8 @@
//--------------------------------------------------------------------------------
// Auto-generated by LiteX (9ad3ecf74) on 2026-09-16 08:16:18
//--------------------------------------------------------------------------------
#ifndef __GENERATED_GIT_H
#define __GENERATED_GIT_H
#define LITEX_GIT_SHA1 "9ad3ecf74"
#endif
@@ -0,0 +1,20 @@
//--------------------------------------------------------------------------------
// Auto-generated by LiteX (9ad3ecf74) on 2026-09-16 08:16:18
//--------------------------------------------------------------------------------
#ifndef __GENERATED_MEM_H
#define __GENERATED_MEM_H
#ifndef CSR_BASE
#define CSR_BASE 0x00000000L
#define CSR_BASE_VA 0x00000000L
#define CSR_SIZE 0x00010000
#endif
#ifndef MEM_REGIONS
#define MEM_REGIONS "CSR 0x00000000 0x10000 "
#endif
#ifndef MEM_REGIONS_DETAILS
#define MEM_REGIONS_DETAILS "Region Origin End Size \nCSR 0x00000000 0x0000ffff 0x10000 "
#endif
#endif
@@ -0,0 +1,115 @@
#ifndef __GENERATED_SDRAM_PHY_H
#define __GENERATED_SDRAM_PHY_H
#include <hw/common.h>
#include <generated/csr.h>
#define DFII_CONTROL_SEL 0x01
#define DFII_CONTROL_CKE 0x02
#define DFII_CONTROL_ODT 0x04
#define DFII_CONTROL_RESET_N 0x08
#define DFII_COMMAND_CS 0x01
#define DFII_COMMAND_WE 0x02
#define DFII_COMMAND_CAS 0x04
#define DFII_COMMAND_RAS 0x08
#define DFII_COMMAND_WRDATA 0x10
#define DFII_COMMAND_RDDATA 0x20
#define SDRAM_PHY_ECP5DDRPHY
#define SDRAM_PHY_XDR 2
#define SDRAM_PHY_DATABITS 16
#define SDRAM_PHY_DFI_DATABITS 64
#define SDRAM_PHY_PHASES 2
#define SDRAM_PHY_CL 6
#define SDRAM_PHY_CWL 5
#define SDRAM_PHY_RDPHASE 0
#define SDRAM_PHY_WRPHASE 1
#define SDRAM_PHY_READ_LEVELING_CAPABLE
#define SDRAM_PHY_DQ_DQS_RATIO 8
#define SDRAM_PHY_MODULES 2
#define SDRAM_PHY_DELAYS 8
#define SDRAM_PHY_BITSLIPS 4
#define SDRAM_PHY_DDR3
#define SDRAM_PHY_SUPPORTED_MEMORY 0x0000000020000000ULL
void cdelay(int i);
__attribute__((unused)) static inline void command_p0(int cmd)
{
sdram_dfii_pi0_command_write(cmd);
sdram_dfii_pi0_command_issue_write(1);
}
__attribute__((unused)) static inline void command_p1(int cmd)
{
sdram_dfii_pi1_command_write(cmd);
sdram_dfii_pi1_command_issue_write(1);
}
#define DFII_PIX_DATA_SIZE CSR_SDRAM_DFII_PI0_WRDATA_SIZE
static inline unsigned long sdram_dfii_pix_wrdata_addr(int phase)
{
switch (phase) {
case 0: return CSR_SDRAM_DFII_PI0_WRDATA_ADDR;
case 1: return CSR_SDRAM_DFII_PI1_WRDATA_ADDR;
default: return 0;
}
}
static inline unsigned long sdram_dfii_pix_rddata_addr(int phase)
{
switch (phase) {
case 0: return CSR_SDRAM_DFII_PI0_RDDATA_ADDR;
case 1: return CSR_SDRAM_DFII_PI1_RDDATA_ADDR;
default: return 0;
}
}
#define DDRX_MR_WRLVL_ADDRESS 1
#define DDRX_MR_WRLVL_RESET 6
#define DDRX_MR_WRLVL_BIT 7
static inline void init_sequence(void)
{
/* Release reset */
sdram_dfii_pi0_address_write(0x0);
sdram_dfii_pi0_baddress_write(0);
sdram_dfii_control_write(DFII_CONTROL_ODT|DFII_CONTROL_RESET_N);
cdelay(50000);
/* Bring CKE high */
sdram_dfii_pi0_address_write(0x0);
sdram_dfii_pi0_baddress_write(0);
sdram_dfii_control_write(DFII_CONTROL_CKE|DFII_CONTROL_ODT|DFII_CONTROL_RESET_N);
cdelay(10000);
/* Load Mode Register 2, CWL=5 */
sdram_dfii_pi0_address_write(0x200);
sdram_dfii_pi0_baddress_write(2);
command_p0(DFII_COMMAND_RAS|DFII_COMMAND_CAS|DFII_COMMAND_WE|DFII_COMMAND_CS);
/* Load Mode Register 3 */
sdram_dfii_pi0_address_write(0x0);
sdram_dfii_pi0_baddress_write(3);
command_p0(DFII_COMMAND_RAS|DFII_COMMAND_CAS|DFII_COMMAND_WE|DFII_COMMAND_CS);
/* Load Mode Register 1 */
sdram_dfii_pi0_address_write(0x6);
sdram_dfii_pi0_baddress_write(1);
command_p0(DFII_COMMAND_RAS|DFII_COMMAND_CAS|DFII_COMMAND_WE|DFII_COMMAND_CS);
/* Load Mode Register 0, CL=6, BL=8 */
sdram_dfii_pi0_address_write(0x320);
sdram_dfii_pi0_baddress_write(0);
command_p0(DFII_COMMAND_RAS|DFII_COMMAND_CAS|DFII_COMMAND_WE|DFII_COMMAND_CS);
cdelay(200);
/* ZQ Calibration */
sdram_dfii_pi0_address_write(0x400);
sdram_dfii_pi0_baddress_write(0);
command_p0(DFII_COMMAND_WE|DFII_COMMAND_CS);
cdelay(200);
}
#endif /* __GENERATED_SDRAM_PHY_H */
@@ -0,0 +1,55 @@
//--------------------------------------------------------------------------------
// Auto-generated by LiteX (9ad3ecf74) on 2026-09-16 08:16:18
//--------------------------------------------------------------------------------
#ifndef __GENERATED_SOC_H
#define __GENERATED_SOC_H
#include <stdint.h>
#define CONFIG_PLATFORM_NAME "platform"
#define CONFIG_CLOCK_FREQUENCY 75000000
#define CONFIG_CPU_TYPE_NONE
#define CONFIG_CPU_VARIANT_STANDARD
#define CONFIG_CPU_FAMILY
#define CONFIG_CPU_NAME "None"
#define CONFIG_CPU_HUMAN_NAME "Unknown"
#define CONFIG_CSR_DATA_WIDTH 32
#define CONFIG_CSR_ALIGNMENT 32
#define CONFIG_CSR_ORDERING_BIG
#define CONFIG_BUS_STANDARD "wishbone"
#define CONFIG_BUS_DATA_WIDTH 32
#define CONFIG_BUS_ADDRESS_WIDTH 32
#define CONFIG_BUS_BURSTING 0
#ifndef __ASSEMBLER__
static inline const char * config_platform_name_read(void) {
return "platform";
}
static inline uint32_t config_clock_frequency_read(void) {
return 75000000;
}
static inline const char * config_cpu_name_read(void) {
return "None";
}
static inline const char * config_cpu_human_name_read(void) {
return "Unknown";
}
static inline uint32_t config_csr_data_width_read(void) {
return 32;
}
static inline uint32_t config_csr_alignment_read(void) {
return 32;
}
static inline const char * config_bus_standard_read(void) {
return "wishbone";
}
static inline uint32_t config_bus_data_width_read(void) {
return 32;
}
static inline uint32_t config_bus_address_width_read(void) {
return 32;
}
static inline uint32_t config_bus_bursting_read(void) {
return 0;
}
#endif // !__ASSEMBLER__
#endif
@@ -0,0 +1,470 @@
BLOCK RESETPATHS;
BLOCK ASYNCPATHS;
LOCATE COMP "clk" SITE "X";
LOCATE COMP "rst" SITE "X";
LOCATE COMP "pll_locked" SITE "X";
LOCATE COMP "ddram_a[0]" SITE "X";
LOCATE COMP "ddram_a[1]" SITE "X";
LOCATE COMP "ddram_a[2]" SITE "X";
LOCATE COMP "ddram_a[3]" SITE "X";
LOCATE COMP "ddram_a[4]" SITE "X";
LOCATE COMP "ddram_a[5]" SITE "X";
LOCATE COMP "ddram_a[6]" SITE "X";
LOCATE COMP "ddram_a[7]" SITE "X";
LOCATE COMP "ddram_a[8]" SITE "X";
LOCATE COMP "ddram_a[9]" SITE "X";
LOCATE COMP "ddram_a[10]" SITE "X";
LOCATE COMP "ddram_a[11]" SITE "X";
LOCATE COMP "ddram_a[12]" SITE "X";
LOCATE COMP "ddram_a[13]" SITE "X";
LOCATE COMP "ddram_a[14]" SITE "X";
LOCATE COMP "ddram_ba[0]" SITE "X";
LOCATE COMP "ddram_ba[1]" SITE "X";
LOCATE COMP "ddram_ba[2]" SITE "X";
LOCATE COMP "ddram_ras_n" SITE "X";
LOCATE COMP "ddram_cas_n" SITE "X";
LOCATE COMP "ddram_we_n" SITE "X";
LOCATE COMP "ddram_cs_n" SITE "X";
LOCATE COMP "ddram_dm[0]" SITE "X";
LOCATE COMP "ddram_dm[1]" SITE "X";
LOCATE COMP "ddram_dq[0]" SITE "X";
LOCATE COMP "ddram_dq[1]" SITE "X";
LOCATE COMP "ddram_dq[2]" SITE "X";
LOCATE COMP "ddram_dq[3]" SITE "X";
LOCATE COMP "ddram_dq[4]" SITE "X";
LOCATE COMP "ddram_dq[5]" SITE "X";
LOCATE COMP "ddram_dq[6]" SITE "X";
LOCATE COMP "ddram_dq[7]" SITE "X";
LOCATE COMP "ddram_dq[8]" SITE "X";
LOCATE COMP "ddram_dq[9]" SITE "X";
LOCATE COMP "ddram_dq[10]" SITE "X";
LOCATE COMP "ddram_dq[11]" SITE "X";
LOCATE COMP "ddram_dq[12]" SITE "X";
LOCATE COMP "ddram_dq[13]" SITE "X";
LOCATE COMP "ddram_dq[14]" SITE "X";
LOCATE COMP "ddram_dq[15]" SITE "X";
LOCATE COMP "ddram_dqs_p[0]" SITE "X";
LOCATE COMP "ddram_dqs_p[1]" SITE "X";
LOCATE COMP "ddram_dqs_n[0]" SITE "X";
LOCATE COMP "ddram_dqs_n[1]" SITE "X";
LOCATE COMP "ddram_clk_p" SITE "X";
LOCATE COMP "ddram_clk_n" SITE "X";
LOCATE COMP "ddram_cke" SITE "X";
LOCATE COMP "ddram_odt" SITE "X";
LOCATE COMP "ddram_reset_n" SITE "X";
LOCATE COMP "init_done" SITE "X";
LOCATE COMP "init_error" SITE "X";
LOCATE COMP "wb_ctrl_adr[0]" SITE "X";
LOCATE COMP "wb_ctrl_adr[1]" SITE "X";
LOCATE COMP "wb_ctrl_adr[2]" SITE "X";
LOCATE COMP "wb_ctrl_adr[3]" SITE "X";
LOCATE COMP "wb_ctrl_adr[4]" SITE "X";
LOCATE COMP "wb_ctrl_adr[5]" SITE "X";
LOCATE COMP "wb_ctrl_adr[6]" SITE "X";
LOCATE COMP "wb_ctrl_adr[7]" SITE "X";
LOCATE COMP "wb_ctrl_adr[8]" SITE "X";
LOCATE COMP "wb_ctrl_adr[9]" SITE "X";
LOCATE COMP "wb_ctrl_adr[10]" SITE "X";
LOCATE COMP "wb_ctrl_adr[11]" SITE "X";
LOCATE COMP "wb_ctrl_adr[12]" SITE "X";
LOCATE COMP "wb_ctrl_adr[13]" SITE "X";
LOCATE COMP "wb_ctrl_adr[14]" SITE "X";
LOCATE COMP "wb_ctrl_adr[15]" SITE "X";
LOCATE COMP "wb_ctrl_adr[16]" SITE "X";
LOCATE COMP "wb_ctrl_adr[17]" SITE "X";
LOCATE COMP "wb_ctrl_adr[18]" SITE "X";
LOCATE COMP "wb_ctrl_adr[19]" SITE "X";
LOCATE COMP "wb_ctrl_adr[20]" SITE "X";
LOCATE COMP "wb_ctrl_adr[21]" SITE "X";
LOCATE COMP "wb_ctrl_adr[22]" SITE "X";
LOCATE COMP "wb_ctrl_adr[23]" SITE "X";
LOCATE COMP "wb_ctrl_adr[24]" SITE "X";
LOCATE COMP "wb_ctrl_adr[25]" SITE "X";
LOCATE COMP "wb_ctrl_adr[26]" SITE "X";
LOCATE COMP "wb_ctrl_adr[27]" SITE "X";
LOCATE COMP "wb_ctrl_adr[28]" SITE "X";
LOCATE COMP "wb_ctrl_adr[29]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[0]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[1]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[2]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[3]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[4]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[5]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[6]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[7]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[8]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[9]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[10]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[11]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[12]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[13]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[14]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[15]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[16]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[17]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[18]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[19]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[20]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[21]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[22]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[23]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[24]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[25]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[26]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[27]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[28]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[29]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[30]" SITE "X";
LOCATE COMP "wb_ctrl_dat_w[31]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[0]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[1]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[2]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[3]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[4]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[5]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[6]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[7]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[8]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[9]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[10]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[11]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[12]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[13]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[14]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[15]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[16]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[17]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[18]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[19]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[20]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[21]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[22]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[23]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[24]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[25]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[26]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[27]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[28]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[29]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[30]" SITE "X";
LOCATE COMP "wb_ctrl_dat_r[31]" SITE "X";
LOCATE COMP "wb_ctrl_sel[0]" SITE "X";
LOCATE COMP "wb_ctrl_sel[1]" SITE "X";
LOCATE COMP "wb_ctrl_sel[2]" SITE "X";
LOCATE COMP "wb_ctrl_sel[3]" SITE "X";
LOCATE COMP "wb_ctrl_cyc" SITE "X";
LOCATE COMP "wb_ctrl_stb" SITE "X";
LOCATE COMP "wb_ctrl_ack" SITE "X";
LOCATE COMP "wb_ctrl_we" SITE "X";
LOCATE COMP "wb_ctrl_cti[0]" SITE "X";
LOCATE COMP "wb_ctrl_cti[1]" SITE "X";
LOCATE COMP "wb_ctrl_cti[2]" SITE "X";
LOCATE COMP "wb_ctrl_bte[0]" SITE "X";
LOCATE COMP "wb_ctrl_bte[1]" SITE "X";
LOCATE COMP "wb_ctrl_err" SITE "X";
LOCATE COMP "user_clk" SITE "X";
LOCATE COMP "user_rst" SITE "X";
LOCATE COMP "user_port_native_0_cmd_valid" SITE "X";
LOCATE COMP "user_port_native_0_cmd_ready" SITE "X";
LOCATE COMP "user_port_native_0_cmd_we" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[0]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[1]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[2]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[3]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[4]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[5]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[6]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[7]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[8]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[9]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[10]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[11]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[12]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[13]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[14]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[15]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[16]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[17]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[18]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[19]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[20]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[21]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[22]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[23]" SITE "X";
LOCATE COMP "user_port_native_0_cmd_addr[24]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_valid" SITE "X";
LOCATE COMP "user_port_native_0_wdata_ready" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[0]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[1]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[2]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[3]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[4]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[5]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[6]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[7]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[8]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[9]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[10]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[11]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[12]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[13]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[14]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_we[15]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[0]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[1]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[2]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[3]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[4]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[5]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[6]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[7]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[8]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[9]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[10]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[11]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[12]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[13]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[14]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[15]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[16]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[17]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[18]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[19]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[20]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[21]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[22]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[23]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[24]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[25]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[26]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[27]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[28]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[29]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[30]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[31]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[32]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[33]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[34]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[35]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[36]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[37]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[38]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[39]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[40]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[41]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[42]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[43]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[44]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[45]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[46]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[47]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[48]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[49]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[50]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[51]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[52]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[53]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[54]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[55]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[56]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[57]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[58]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[59]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[60]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[61]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[62]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[63]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[64]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[65]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[66]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[67]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[68]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[69]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[70]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[71]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[72]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[73]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[74]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[75]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[76]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[77]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[78]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[79]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[80]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[81]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[82]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[83]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[84]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[85]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[86]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[87]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[88]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[89]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[90]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[91]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[92]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[93]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[94]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[95]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[96]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[97]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[98]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[99]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[100]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[101]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[102]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[103]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[104]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[105]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[106]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[107]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[108]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[109]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[110]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[111]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[112]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[113]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[114]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[115]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[116]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[117]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[118]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[119]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[120]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[121]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[122]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[123]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[124]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[125]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[126]" SITE "X";
LOCATE COMP "user_port_native_0_wdata_data[127]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_valid" SITE "X";
LOCATE COMP "user_port_native_0_rdata_ready" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[0]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[1]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[2]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[3]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[4]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[5]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[6]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[7]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[8]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[9]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[10]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[11]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[12]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[13]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[14]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[15]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[16]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[17]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[18]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[19]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[20]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[21]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[22]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[23]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[24]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[25]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[26]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[27]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[28]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[29]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[30]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[31]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[32]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[33]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[34]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[35]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[36]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[37]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[38]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[39]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[40]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[41]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[42]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[43]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[44]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[45]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[46]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[47]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[48]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[49]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[50]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[51]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[52]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[53]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[54]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[55]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[56]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[57]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[58]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[59]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[60]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[61]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[62]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[63]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[64]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[65]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[66]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[67]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[68]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[69]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[70]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[71]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[72]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[73]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[74]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[75]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[76]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[77]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[78]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[79]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[80]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[81]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[82]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[83]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[84]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[85]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[86]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[87]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[88]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[89]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[90]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[91]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[92]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[93]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[94]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[95]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[96]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[97]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[98]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[99]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[100]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[101]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[102]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[103]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[104]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[105]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[106]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[107]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[108]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[109]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[110]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[111]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[112]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[113]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[114]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[115]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[116]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[117]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[118]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[119]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[120]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[121]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[122]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[123]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[124]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[125]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[126]" SITE "X";
LOCATE COMP "user_port_native_0_rdata_data[127]" SITE "X";
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+164
View File
@@ -0,0 +1,164 @@
`timescale 1ns/1ps
// ============================================================
// DDR3 exploration -- real measured sustained bandwidth of the
// generated litedram_core_sim.v (LiteDRAM standalone core, ECP5DDRPHY,
// DDR3 MT41K256M16 x16/512MB, sys_clk_freq=75MHz -- config in
// hardware/v2/ddr3/litedram_gen/ecp5_85f_ddr3_mt41k256m16.yml, the
// SAME real chip+clock the real ECPIX-5 board ships with, not a
// tuned/optimistic guess).
//
// Drives the native port (cmd/wdata/rdata, standard LiteX stream
// handshake -- see litedram/common.py's own cmd_description/
// wdata_description/rdata_description) directly, in strict lockstep
// per transaction (issue cmd, wait ready, push/pull the matching data
// phase, wait ready) -- this is a conservative lower bound on
// achievable bandwidth (no command pipelining attempted), reported as
// such, not claimed as the ceiling.
//
// N_TRANSACTIONS sequential 128-bit (16-byte) writes, then the same
// addresses read back and checked bit-exact against the write
// pattern, with real cycle counts converted to real MB/s using the
// declared sys_clk_freq.
// ============================================================
module tb;
localparam ADDR_WIDTH = 25;
localparam DATA_WIDTH = 128;
localparam WE_WIDTH = DATA_WIDTH/8;
localparam real SYS_CLK_FREQ_MHZ = 75.0;
localparam real CLK_PERIOD_NS = 1000.0/SYS_CLK_FREQ_MHZ;
reg clk = 0;
always #(CLK_PERIOD_NS/2.0) clk = ~clk;
integer cyc;
always @(posedge clk) cyc <= cyc + 1;
wire init_done, init_error, user_clk, user_rst;
reg [ADDR_WIDTH-1:0] cmd_addr;
wire cmd_ready;
reg cmd_valid, cmd_we;
wire [DATA_WIDTH-1:0] rdata_data;
reg rdata_ready;
wire rdata_valid;
reg [DATA_WIDTH-1:0] wdata_data;
wire wdata_ready;
reg wdata_valid;
reg [WE_WIDTH-1:0] wdata_we;
// wb_ctrl_* left disconnected (tied off) -- this benchmark drives
// the native port only, no CSR/wishbone control path needed.
wire wb_ctrl_ack, wb_ctrl_err;
wire [31:0] wb_ctrl_dat_r;
litedram_core_sim dut (
.clk(clk),
.init_done(init_done), .init_error(init_error),
.sim_trace(1'b0),
.user_clk(user_clk), .user_rst(user_rst),
.user_port_native_0_cmd_addr(cmd_addr),
.user_port_native_0_cmd_ready(cmd_ready),
.user_port_native_0_cmd_valid(cmd_valid),
.user_port_native_0_cmd_we(cmd_we),
.user_port_native_0_rdata_data(rdata_data),
.user_port_native_0_rdata_ready(rdata_ready),
.user_port_native_0_rdata_valid(rdata_valid),
.user_port_native_0_wdata_data(wdata_data),
.user_port_native_0_wdata_ready(wdata_ready),
.user_port_native_0_wdata_valid(wdata_valid),
.user_port_native_0_wdata_we(wdata_we),
.wb_ctrl_ack(wb_ctrl_ack), .wb_ctrl_adr(30'h0), .wb_ctrl_bte(2'h0),
.wb_ctrl_cti(3'h0), .wb_ctrl_cyc(1'b0), .wb_ctrl_dat_r(wb_ctrl_dat_r),
.wb_ctrl_dat_w(32'h0), .wb_ctrl_err(wb_ctrl_err), .wb_ctrl_sel(4'h0),
.wb_ctrl_stb(1'b0), .wb_ctrl_we(1'b0)
);
task automatic do_write(input [ADDR_WIDTH-1:0] a, input [DATA_WIDTH-1:0] d);
begin
cmd_valid = 1'b1; cmd_we = 1'b1; cmd_addr = a;
@(posedge clk);
while (!cmd_ready) @(posedge clk);
cmd_valid = 1'b0;
wdata_valid = 1'b1; wdata_data = d; wdata_we = {WE_WIDTH{1'b1}};
@(posedge clk);
while (!wdata_ready) @(posedge clk);
wdata_valid = 1'b0;
end
endtask
task automatic do_read(input [ADDR_WIDTH-1:0] a, output [DATA_WIDTH-1:0] d);
begin
cmd_valid = 1'b1; cmd_we = 1'b0; cmd_addr = a;
@(posedge clk);
while (!cmd_ready) @(posedge clk);
cmd_valid = 1'b0;
rdata_ready = 1'b1;
while (!rdata_valid) @(posedge clk);
d = rdata_data;
@(posedge clk);
rdata_ready = 1'b0;
end
endtask
localparam N_TRANSACTIONS = 256;
integer i, t0, t1, write_cycles, read_cycles, errors;
reg [DATA_WIDTH-1:0] got;
real write_mb_s, read_mb_s;
initial begin
cmd_valid = 0; cmd_we = 0; cmd_addr = 0;
wdata_valid = 0; wdata_data = 0; wdata_we = 0;
rdata_ready = 0;
errors = 0; cyc = 0;
// NOTE: this is a CPU-less standalone core (cpu: None) -- init_done
// is normally driven by BIOS software over the wishbone CSR bus
// (real litedram known behavior, see enjoy-digital/litedram
// issue #106 / PR #286: "enable the user port unconditionally in
// CPU-less cases"). With no CPU attached, init_done never
// asserts on its own -- the user port is intentionally usable
// without waiting for it in this configuration. Give the
// power-on reset counters real time to settle, then proceed.
$display("=== CPU-less core: not gating on init_done (see litedram issue #106) -- settling power-on reset ===");
repeat(2000) @(posedge clk);
$display(" init_done=%b init_error=%b at cycle %0d (%0.2f us) -- proceeding regardless (informational only)",
init_done, init_error, cyc, cyc*CLK_PERIOD_NS/1000.0);
$display("=== WRITE: %0d sequential 128-bit transactions ===", N_TRANSACTIONS);
t0 = cyc;
for (i = 0; i < N_TRANSACTIONS; i = i + 1)
do_write(i, {8{16'(16'hA000 + i)}});
t1 = cyc;
write_cycles = t1 - t0;
write_mb_s = (N_TRANSACTIONS * (DATA_WIDTH/8)) / (write_cycles * CLK_PERIOD_NS / 1000.0) / 1.0e6 * 1.0e6;
// (bytes) / (seconds) -> bytes/s; convert to MB/s
write_mb_s = (N_TRANSACTIONS * (DATA_WIDTH/8) * 1.0) / (write_cycles * CLK_PERIOD_NS * 1.0e-9) / 1.0e6;
$display(" %0d cycles, %00.3f us, REAL measured write bandwidth = %0.2f MB/s",
write_cycles, write_cycles*CLK_PERIOD_NS/1000.0, write_mb_s);
$display("=== READ: %0d sequential 128-bit transactions, bit-exact check ===", N_TRANSACTIONS);
t0 = cyc;
for (i = 0; i < N_TRANSACTIONS; i = i + 1) begin
do_read(i, got);
if (got !== {8{16'(16'hA000 + i)}}) begin
$display("FAIL addr=%0d got=%h", i, got);
errors = errors + 1;
end
end
t1 = cyc;
read_cycles = t1 - t0;
read_mb_s = (N_TRANSACTIONS * (DATA_WIDTH/8) * 1.0) / (read_cycles * CLK_PERIOD_NS * 1.0e-9) / 1.0e6;
$display(" %0d cycles, %0.3f us, REAL measured read bandwidth = %0.2f MB/s",
read_cycles, read_cycles*CLK_PERIOD_NS/1000.0, read_mb_s);
$display("=== %0d/%0d bit-exact, %0d errors ===", N_TRANSACTIONS-errors, N_TRANSACTIONS, errors);
if (errors == 0) $display("ALL DATA BIT-EXACT (tb_litedram_bandwidth)");
$finish;
end
initial begin
#2000000; // 2ms real-time safety watchdog
$display("WATCHDOG TIMEOUT -- init_done never asserted or benchmark hung");
$finish;
end
endmodule
+137
View File
@@ -3345,3 +3345,140 @@ hardware/v2/nms/sim/tb_sdram_cdc_bridge_openrow.v,
hardware/v2/nms/sim/tb_nms_dstress_sdram_combined.v, hardware/v2/nms/sim/tb_nms_dstress_sdram_combined.v,
hardware/v2/nms/sim/tb_nms_dstress_sdram_openrow.v, hardware/v2/nms/sim/tb_nms_dstress_sdram_openrow.v,
hardware/v2/nms/sim/tb_nms_dstress_sdram_cdc.v. hardware/v2/nms/sim/tb_nms_dstress_sdram_cdc.v.
EXP-0056 -- N_SLOTS=16 timing closure on LFE5U-85F: two real fixes,
one didn't matter, one did (2026-09-16)
DATE: 2026-09-16
CONTEXT: EXP-0055 (open-row backend, real board top) promoted to a
candidate but never checked at N_SLOTS=16 -- real 8-seed P&R baseline
(EXP-0049/0050-era numbers) never covered N=16 either. Real synthesis
+ nextpnr-ecp5 --85k (LFE5U-85F, same CABGA381 package/pinout as the
real board's v2_board_top.lpf -- confirmed pin-compatible) at
N_SLOTS=16: worst 23.52-24.64MHz across two independent seeds, FAIL at
the real 64MHz target. DSP fit confirmed fine (128/156 MULT18X18D,
82%) -- this is a timing-closure problem, not a resource problem.
HYPOTHESIS 1 (wrong, but real work, kept as a disclosed negative
result): dependency_manager.v's own first_ready_idx scan (serial
for-loop over up to N_NODES=1024, same architectural anti-pattern
already fixed twice elsewhere -- ERR-0027/ERR-0028/ERR-0029). Built
priority_encoder_lsb.v (generic recursive binary-tree lowest-set-bit
encoder, O(log2(WIDTH)) depth) + dependency_manager_fast.v (fork,
swaps in the encoder). Isolated: 65536/65536 exhaustive at WIDTH=16,
76562/76562 at WIDTH=1024. Bit-exact equivalence vs the original
module: 20000/20000 cycles matched under random stimulus
(tb_dependency_manager_fast.v), plus the original hand-crafted DAG
testbench, both 100%. Integrated (N_SLOTS=16, LFE5U-85F): worst
24.26MHz -- ESSENTIALLY UNCHANGED from the pre-fix 23.52-24.64MHz.
CONCLUSION: dependency_manager.v was not the real N=16 bottleneck.
Kept as a real, verified, low-risk correctness-neutral improvement
(shorter combinational depth is never worse), just not the fix that
mattered here.
HYPOTHESIS 2 (real root cause, found from the actual nextpnr critical-
path report on the Hypothesis-1 run): nms_activation_fill_ctrl_v3.v's
own balanced max-tree (a real fix from an EARLIER session, its own
header says so explicitly) was hand-coded ONLY for N_SLOTS in
{1,2,4,8} -- any other value, INCLUDING N_SLOTS=16, falls through to
GEN_MAXTREE_FALLBACK, the exact same flat N_SLOTS-wide sequential
scan/carry-chain that earlier fix was written to eliminate. Never
extended to cover 16. Real critical path (nextpnr's own report,
Hypothesis-1 run): a long CCU2C COUT/CIN carry chain inside
nms_activation_fill_ctrl_v3.v's max_n_tiles_reg comparator, confirming
this exactly.
FIX: nms_activation_fill_ctrl_v3_n16.v (fork), added the missing
N_SLOTS==16 case -- same balanced-tree pattern as the existing N==8
case, one more level (8 pairwise compares -> 4 -> 2 -> 1, 4 levels
total). Isolated: tb_maxtree_n16.v, 10017/10017 (targeted + random)
against the same flat-scan reference the fallback path itself uses as
its own documented "correct but not optimized" baseline.
RESULT (N_SLOTS=16, LFE5U-85F, seed 1, both fixes combined --
dependency_manager_fast + activation_fill_ctrl_v3_n16 -- in nms_
dataflow_core_sdram_fast.v / fpga_neural_v2_top_openrow_fast.v):
worst 71.01MHz, PASS at 64MHz. 0 errors. Functional regression
unaffected: D-Stress N=16 still 256/256 bit-exact, total_cycles=47454
(identical to the pre-fix functional baseline, as expected -- these
are pure combinational-depth fixes, not behavior changes) -- and still
confirms N=16 gives ZERO extra real throughput over N=4/N=8 on the
zero-reuse D-Stress workload (memory-bound, unrelated to this fix).
STATUS: single-seed PASS, not yet the project's own 8-seed standard.
next_action: run the full 8-seed sweep before treating N=16 as a
closed, production-ready configuration. New files (additive only,
none touch the real board top or existing production RTL):
hardware/v2/rtl/priority_encoder_lsb.v,
hardware/v2/rtl/dependency_manager_fast.v,
hardware/v2/nms/rtl/nms_activation_fill_ctrl_v3_n16.v,
hardware/v2/nms/rtl/nms_dataflow_core_sdram_fast.v,
hardware/v2/nms/rtl/fpga_neural_v2_top_openrow_fast.v,
hardware/v2/nms/rtl/nms_neural_multiprocessor_sdram_openrow_fast.v,
hardware/v2/sim/tb_priority_encoder_lsb.v,
hardware/v2/sim/tb_dependency_manager_fast.v,
hardware/v2/sim/tb_maxtree_n16.v,
hardware/v2/nms/sim/tb_fpga_neural_v2_top_openrow_fast_smoke.v,
hardware/v2/nms/sim/tb_nms_dstress_sdram_openrow_fast.v.
EXP-0057 -- weight-stationary layer reuse: real measured 7.16x memory-
side speedup, SAME hardware, no DDR3 (2026-09-16)
DATE: 2026-09-16
CONTEXT: user-driven pivot after establishing the real target
application class (generic neural accelerator for face-recognition-
style CNNs, not the zero-reuse D-Stress worst case this whole project
has been benchmarked against). D-Stress's own zero reuse means no
architecture can beat the physical bandwidth floor (established
earlier this session); a real conv-style workload has massive weight
reuse (same filter applied at every spatial position) that D-Stress
deliberately excludes -- this experiment measures that case for real,
on the SAME SDR SDRAM hardware this project already has (no DDR3, no
clock change), to answer directly whether DDR3 is even necessary for
a workload class that actually has reuse.
METHOD: layer_weight_buffer.v (new) -- double-buffered, per-layer
resident weight scratchpad (BRAM-style, same coding idiom as nms_
weight_packed.v). One buffer read many times (M reuses) while the
OTHER is filled in the background from SDRAM; swap is order-
independent (fill_done/consume_done latched separately, swap fires
once both have been seen since the last swap -- same req_pending-latch
discipline as sdram_unified_backend.v's own established correctness
fixes). Isolated: tb_layer_weight_buffer.v, 1540/1540, including
out-of-order fill/consume completion and a "consume_done alone must
not swap without a matching fill_done" negative check.
tb_layer_reuse_vs_zero_reuse.v: wired layer_weight_buffer.v to the
REAL sdram_controller_openrow.v (EXP-0054) + sdram_model.v -- same
hardware, nothing new. SAME total useful-byte-consumption in both
cases (32768 bytes, matching D-Stress's own 256x128 total exactly):
REUSE case: 16 layers x 128 bytes each fetched ONCE, reused 16x
locally = 2048 bytes actually fetched from SDRAM.
ZERO-REUSE case: 16 layers x 16 reuses x 128 bytes, every reuse
fetched independently = 32768 bytes (D-Stress's
own pattern, through the identical controller).
Fair timing comparison (an earlier version of this testbench asymmetrically
added compute-side consumption cycles to only the reuse case, making it
look SLOWER -- found and fixed before trusting any number; final
version measures ONLY real SDRAM fetch cost in both cases, which is
the actual question this experiment exists to answer).
RESULT: REUSE case data correctness 32768/32768, 0 errors, through the
real controller+model. Real measured cycles: REUSE = 3777, ZERO-REUSE
= 27048 (for the identical 32768 bytes of useful data delivered) --
**7.16x real measured speedup from weight reuse alone**, same SDR
SDRAM, same 64MHz clock, zero new hardware. Substantially larger than
any protocol-level lever measured this session (open-row +5%, dual-
bank ~9%, CDC net-negative) -- because this reduces bytes actually
moved rather than trying to move the same bytes faster.
DECISION: for workload classes with real reuse (conv-style, unlike
D-Stress), DDR3 is NOT established as necessary -- this result directly
contradicts the earlier (correct, but scope-limited-to-zero-reuse)
conclusion that only more physical bandwidth could help. DDR3 remains
relevant only if a real target model's per-layer working set exceeds
what layer-by-layer streaming + on-chip BRAM can hold, which depends
on the real model size (still not pinned down as of this entry).
next_action: integrate layer_weight_buffer.v with the real per-slot
compute path (neural_processor.v) and a real conv-shaped benchmark
(not just the synthetic byte-reuse pattern here) before calling this
production-ready. New files (additive only):
hardware/v2/rtl/layer_weight_buffer.v,
hardware/v2/sim/tb_layer_weight_buffer.v,
hardware/v2/sim/tb_layer_reuse_vs_zero_reuse.v.
@@ -0,0 +1,265 @@
`timescale 1ns/1ps
// ================================================================
// FPGA-Neural V2 -- BOARD-LEVEL TOP (STEP20, real physical interface)
//
// Wraps the STEP19 frozen compute+memory design (the same submodules
// nms_neural_multiprocessor_sdram_unified.v instantiates -- that file
// itself is NOT instantiated here, since its own reg_*/N_SLOTS+1-port
// AR arbitration needs a second arbitration LEVEL added for the new
// host-raw-SDRAM-access port; this module reproduces that same
// internal wiring plus the extra level, rather than modifying the
// frozen file) with the three things a real physical board needs that
// a testbench does not:
//
// 1. A real SPI host interface (spi_host_bridge.v) in place of the
// 110-pin reg_* testbench bus -- reg_valid/reg_ready/reg_node_id/
// etc are now DRIVEN BY THE BRIDGE, not exposed as top ports.
// 2. A real ECP5 PLL (ecp5_pll_sys_clk.v, EHXPLLL) generating the
// system clock from the board's 16MHz oscillator, instead of
// assuming an already-correct-frequency clock input.
// 3. A real reset/POR synchronizer (reset_sync.v).
//
// nms_dataflow_core_sdram.v, dependency_manager.v, neural_processor.v,
// neural_director.v, slot_mem_arbiter.v, slot_mem_arbiter_wide.v,
// sdram_unified_backend.v, sdram_controller.v are ALL byte-for-byte
// unchanged (STEP19/STEP20 standing constraint) -- this file only
// ADDS one more, already-proven, generically-parameterized
// slot_mem_arbiter instance (N_PORTS=2) to arbitrate the SPI bridge's
// raw host memory port against the existing compute-side AR stream,
// both funneling into the SAME single sdram_unified_backend/
// sdram_controller/AS4C4M16SA-6TIN physical chain STEP19 already
// validated. No V1 RTL is instantiated (STEP19's "zero V1 files in
// the V2 compile list" property is preserved).
// ================================================================
module fpga_neural_v2_top_openrow_fast #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ACC_WIDTH = 32,
parameter ADDR_WIDTH = 26,
parameter N_SLOTS = 4,
parameter N_NODES = 16,
parameter MAX_DEPS = 4,
parameter QUEUE_DEPTH = 8,
parameter MAX_TILES = 16,
parameter PREFETCH_DISTANCE = 8,
parameter CLK_FREQ_MHZ = 64
)(
input wire osc_clk, // 16 MHz board oscillator
input wire ext_rst_n, // external POR/supervisor, active-low
// ---- physical SPI host interface ----
input wire spi_sclk,
input wire spi_mosi,
output wire spi_miso,
input wire spi_cs_n,
// ---- single physical SDRAM (weights + activations + results) ----
// sdram_clk: the real SDRAM chip's own CLK pin -- an external
// chip, it needs this driven from a real output ball, NOT just
// internal routing. Found missing entirely during this session's
// schematic review (clk_sys was purely internal, never reached a
// pad) -- added here, real free clock-capable ball (bank 6).
output wire sdram_clk,
output wire sdram_cke,
output wire sdram_cs_n,
output wire sdram_ras_n,
output wire sdram_cas_n,
output wire sdram_we_n,
output wire [1:0] sdram_ba,
output wire [12:0] sdram_a,
inout wire [15:0] sdram_dq,
output wire [1:0] sdram_dqm,
// FPGA_DATA_READY: high once the whole registered graph has
// finished (system-idle sticky flag, self-clearing on new work) --
// see nms_dataflow_core_sdram.v for the full design comment.
output wire data_ready,
output wire pll_locked
);
// ============================================================
// CLOCK / RESET
// ============================================================
wire clk_sys;
ecp5_pll_sys_clk u_pll (
.clk_16mhz(osc_clk), .clk_sys(clk_sys), .locked(pll_locked)
);
assign sdram_clk = clk_sys;
wire clk = clk_sys;
wire rst;
reset_sync u_reset_sync (
.clk_sys(clk_sys), .ext_rst_n(ext_rst_n), .pll_locked(pll_locked), .rst(rst)
);
wire soft_rst_pulse;
wire core_rst = rst | soft_rst_pulse;
// ============================================================
// SPI HOST BRIDGE (replaces the 110-pin reg_* testbench bus)
// ============================================================
wire reg_valid, reg_ready;
wire [$clog2(N_NODES)-1:0] reg_node_id;
wire [$clog2(MAX_DEPS+1)-1:0] reg_required;
wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids;
wire [ADDR_WIDTH-1:0] reg_x_base, reg_w_base, reg_result_addr;
wire [15:0] reg_n_tiles;
wire host_mem_req, host_mem_wr, host_mem_lb_n, host_mem_ub_n;
wire [ADDR_WIDTH-1:0] host_mem_addr;
wire [15:0] host_mem_wdata, host_mem_rdata;
wire host_mem_ready;
spi_host_bridge #(
.ADDR_WIDTH(ADDR_WIDTH), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS)
) u_spi_bridge (
.clk(clk), .rst(rst),
.sclk(spi_sclk), .mosi(spi_mosi), .miso(spi_miso), .cs_n(spi_cs_n),
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base),
.reg_n_tiles(reg_n_tiles), .reg_result_addr(reg_result_addr),
.mem_req(host_mem_req), .mem_wr(host_mem_wr), .mem_addr(host_mem_addr),
.mem_wdata(host_mem_wdata), .mem_lb_n(host_mem_lb_n), .mem_ub_n(host_mem_ub_n),
.mem_rdata(host_mem_rdata), .mem_ready(host_mem_ready),
.soft_rst_pulse(soft_rst_pulse)
);
// ============================================================
// COMPUTE + MEMORY (same wiring as nms_neural_multiprocessor_
// sdram_unified.v, plus the new host-arb level)
// ============================================================
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
wire [N_SLOTS:0] slot_mem_ready;
wire [N_SLOTS-1:0] wide_slot_mem_req;
wire [ADDR_WIDTH*N_SLOTS-1:0] wide_slot_mem_addr;
wire [64*N_SLOTS-1:0] wide_slot_mem_rdata;
wire [N_SLOTS-1:0] wide_slot_mem_ready;
nms_dataflow_core_sdram_fast #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
) u_dataflow_core (
.clk(clk), .rst(core_rst),
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
.reg_result_addr(reg_result_addr),
.data_ready(data_ready),
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready),
.wide_slot_mem_req(wide_slot_mem_req), .wide_slot_mem_addr(wide_slot_mem_addr),
.wide_slot_mem_rdata(wide_slot_mem_rdata), .wide_slot_mem_ready(wide_slot_mem_ready)
);
// ---- AR level 1 (unchanged): activation-fill + per-slot result
// writeback, exactly as nms_neural_multiprocessor_sdram_unified.v ----
wire arb_m_req, arb_m_wr;
wire [ADDR_WIDTH-1:0] arb_m_addr;
wire [15:0] arb_m_wdata;
wire arb_m_lb_n, arb_m_ub_n;
wire [15:0] arb_m_rdata;
wire arb_m_ready;
slot_mem_arbiter #(
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
) u_arbiter (
.clk(clk), .rst(core_rst),
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
);
// ---- AR level 2 (NEW, STEP20): compute-side AR stream (port0)
// vs. SPI host raw memory port (port1) -- reuses slot_mem_arbiter
// completely unchanged, just at N_PORTS=2, its own already-proven
// pending-latch discipline applying equally to a 2-port instance ----
wire [1:0] host_arb_s_req, host_arb_s_wr, host_arb_s_lb_n, host_arb_s_ub_n, host_arb_s_ready;
wire [ADDR_WIDTH*2-1:0] host_arb_s_addr;
wire [16*2-1:0] host_arb_s_wdata, host_arb_s_rdata;
assign host_arb_s_req = {host_mem_req, arb_m_req};
assign host_arb_s_wr = {host_mem_wr, arb_m_wr};
assign host_arb_s_lb_n = {host_mem_lb_n, arb_m_lb_n};
assign host_arb_s_ub_n = {host_mem_ub_n, arb_m_ub_n};
assign host_arb_s_addr = {host_mem_addr, arb_m_addr};
assign host_arb_s_wdata = {host_mem_wdata, arb_m_wdata};
assign arb_m_ready = host_arb_s_ready[0];
assign arb_m_rdata = host_arb_s_rdata[15:0];
assign host_mem_ready = host_arb_s_ready[1];
assign host_mem_rdata = host_arb_s_rdata[31:16];
wire final_ar_req, final_ar_wr;
wire [ADDR_WIDTH-1:0] final_ar_addr;
wire [15:0] final_ar_wdata;
wire final_ar_lb_n, final_ar_ub_n;
wire [15:0] final_ar_rdata;
wire final_ar_ready;
slot_mem_arbiter #(
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(2)
) u_host_arb (
.clk(clk), .rst(core_rst),
.s_req(host_arb_s_req), .s_wr(host_arb_s_wr), .s_addr(host_arb_s_addr),
.s_wdata(host_arb_s_wdata), .s_lb_n(host_arb_s_lb_n), .s_ub_n(host_arb_s_ub_n),
.s_rdata(host_arb_s_rdata), .s_ready(host_arb_s_ready),
.m_req(final_ar_req), .m_wr(final_ar_wr), .m_addr(final_ar_addr), .m_wdata(final_ar_wdata),
.m_lb_n(final_ar_lb_n), .m_ub_n(final_ar_ub_n),
.m_rdata(final_ar_rdata), .m_ready(final_ar_ready)
);
// ---- W: weight fetch (unchanged) ----
wire [N_SLOTS-1:0] wide_s_wr = {N_SLOTS{1'b0}};
wire [64*N_SLOTS-1:0] wide_s_wdata = {(64*N_SLOTS){1'b0}};
wire [N_SLOTS-1:0] wide_s_lb_n = {N_SLOTS{1'b0}};
wire [N_SLOTS-1:0] wide_s_ub_n = {N_SLOTS{1'b0}};
wire wide_arb_m_req, wide_arb_m_wr;
wire [ADDR_WIDTH-1:0] wide_arb_m_addr;
wire [63:0] wide_arb_m_wdata;
wire wide_arb_m_lb_n, wide_arb_m_ub_n;
wire [63:0] wide_arb_m_rdata;
wire wide_arb_m_ready;
slot_mem_arbiter_wide #(
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS), .DATA_WIDTH(64)
) u_arbiter_wide (
.clk(clk), .rst(core_rst),
.s_req(wide_slot_mem_req), .s_wr(wide_s_wr), .s_addr(wide_slot_mem_addr),
.s_wdata(wide_s_wdata), .s_lb_n(wide_s_lb_n), .s_ub_n(wide_s_ub_n),
.s_rdata(wide_slot_mem_rdata), .s_ready(wide_slot_mem_ready),
.m_req(wide_arb_m_req), .m_wr(wide_arb_m_wr), .m_addr(wide_arb_m_addr), .m_wdata(wide_arb_m_wdata),
.m_lb_n(wide_arb_m_lb_n), .m_ub_n(wide_arb_m_ub_n),
.m_rdata(wide_arb_m_rdata), .m_ready(wide_arb_m_ready)
);
// ---- ONE physical SDRAM backend, both W and (now 2-source-
// arbitrated) AR ports ----
sdram_unified_backend_openrow #(
.ADDR_WIDTH(ADDR_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
) u_sdram_backend (
.clk(clk), .rst(core_rst),
.w_req(wide_arb_m_req), .w_addr(wide_arb_m_addr),
.w_rdata(wide_arb_m_rdata), .w_ready(wide_arb_m_ready),
.ar_req(final_ar_req), .ar_wr(final_ar_wr), .ar_addr(final_ar_addr), .ar_wdata(final_ar_wdata),
.ar_lb_n(final_ar_lb_n), .ar_ub_n(final_ar_ub_n),
.ar_rdata(final_ar_rdata), .ar_ready(final_ar_ready),
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
);
endmodule
@@ -0,0 +1,310 @@
// ============================================================
// Neural Memory System (NMS) -- shared Activation fill controller.
//
// One instance per neural_memory_system (shared across all N_SLOTS),
// backing nms_activation_replicated.v's single broadcast-write fill
// port. Owns ONE prefetch_engine.v instance (real word-level PSRAM
// fetch, DEC-0015 convention, reused verbatim -- it is generic
// P_IN-byte-tile fetch logic, not weight-specific despite its name).
//
// Single-tag design (same honest limitation as the superseded
// hardware/v2/rtl/activation_cache.v, DEC-0016): tracks ONE resident
// x_base at a time. Refills (resident_count resets to 0, restarts
// fetching from tile 0) whenever the lowest-indexed currently-active
// slot's own x_base differs from what is resident -- correct always,
// but can thrash under interleaved, genuinely-different-x_base
// concurrent traffic; not exercised by this project's own realistic
// dense-layer workloads (shared-producer dispatch, many slots given
// the SAME x_base together).
//
// resident_count extends to the MAX n_tiles needed by any currently
// active slot that shares resident_tag (not just the reference slot
// that triggered the refill), so a later-joining slot with a deeper
// need is served without a second refill.
// ============================================================
module nms_activation_fill_ctrl_v3_n16 #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter N_SLOTS = 4,
parameter ADDR_WIDTH = 26,
parameter MAX_TILES = 16,
// TIW indexes the SRAM fill address (0..MAX_TILES-1); CNTW is for
// resident_count, which must represent the VALUE MAX_TILES itself
// (e.g. a fully-resident 16-tile vector with MAX_TILES=16) -- one
// bit wider than TIW, same distinction/bug as
// nms_memory_manager.v's own tile_idx/wgt_fetched (see that file's
// header for the real deadlock this caused before the fix).
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
parameter CNTW = $clog2(MAX_TILES+1)
)(
input wire clk,
input wire rst,
// ---- per-slot job status (levels, held while that slot's job is active) ----
input wire [N_SLOTS-1:0] job_active,
input wire [N_SLOTS*ADDR_WIDTH-1:0] x_base_flat,
input wire [N_SLOTS*16-1:0] n_tiles_flat,
// ---- broadcast status (every slot compares this against its own x_base) ----
output reg [ADDR_WIDTH-1:0] resident_tag,
output reg [CNTW-1:0] resident_count,
// ---- fill port into nms_activation_replicated.v ----
output wire fill_we,
output wire [TIW-1:0] fill_addr,
output wire [DATA_WIDTH*P_IN-1:0] fill_data,
// ---- real word-level PSRAM backend (arbitrated externally) ----
output wire mem_req,
output wire mem_wr,
output wire [ADDR_WIDTH-1:0] mem_addr,
output wire [15:0] mem_wdata,
output wire mem_lb_n,
output wire mem_ub_n,
input wire [15:0] mem_rdata,
input wire mem_ready
);
integer i;
// ---- desired x_base: lowest-indexed currently-active slot (fixed
// priority -- simple, not fairness-critical here since this only
// decides which TAG to chase, not who gets bandwidth) ----
reg desired_valid;
reg [ADDR_WIDTH-1:0] desired_x_base;
always @* begin
desired_valid = 1'b0;
desired_x_base = {ADDR_WIDTH{1'b0}};
for (i = N_SLOTS-1; i >= 0; i = i - 1) begin
if (job_active[i]) begin
desired_valid = 1'b1;
desired_x_base = x_base_flat[i*ADDR_WIDTH +: ADDR_WIDTH];
end
end
end
// ---- STEP14/EXP-0029->EXP-0030: max_n_tiles computation split
// into TWO pipeline stages, since registering ONLY its final use
// (v2, DEC-0026) left the computation ITSELF as the new critical
// path (EXP-0030, N=4 Fmax=72.78MHz, still FAIL@80MHz): the
// original single-cycle logic mixed, PER SLOT, a 23-bit tag
// equality check (x_base_flat[i]==resident_tag) together with an
// N_SLOTS-wide SEQUENTIALLY-CHAINED 16-bit running-max fold (each
// iteration's update depends on the previous one) -- both
// combinational, both in the same cycle as the register that
// captures the result.
//
// Stage 1 (independent per-slot work, no chain dependency between
// slots): register a per-slot "counts toward this refill" mask
// (job_active[i] && tag-match) and, gated by that mask, each
// slot's own n_tiles value (0 if it doesn't count) -- N_SLOTS
// independent 23-bit equality checks, no data dependency between
// slots, so their combined depth does not grow with N_SLOTS the
// way a sequential fold does.
// Stage 2 (the actual reduction): fold the REGISTERED, already-
// masked per-slot values into max_n_tiles_reg -- still an
// N_SLOTS-wide sequential chain (same fold as before), but now
// operating alone, without the equality check sharing the same
// cycle.
reg [15:0] n_tiles_masked [0:N_SLOTS-1];
genvar gsi;
generate
for (gsi = 0; gsi < N_SLOTS; gsi = gsi + 1) begin : GEN_MASK
wire slot_counts = job_active[gsi] &&
(x_base_flat[gsi*ADDR_WIDTH +: ADDR_WIDTH] == resident_tag);
always @(posedge clk) begin
if (rst) n_tiles_masked[gsi] <= 16'h0;
else n_tiles_masked[gsi] <= slot_counts ? n_tiles_flat[gsi*16 +: 16] : 16'h0;
end
end
endgenerate
// Balanced binary max-tree (log2(N_SLOTS) comparison levels)
// instead of the flat N_SLOTS-wide sequential scan this file's own
// header comment above already flagged as "an N_SLOTS-wide
// sequential chain". Found and fixed this session: that chain's
// own carry-chain critical path became the DOMINANT critical path
// at N_SLOTS=8 (real nextpnr-ecp5 P&R: Fmax collapsed to ~40MHz,
// failing the 64MHz target across every measured seed). A tree
// has the SAME single-cycle combinational timing as the scan it
// replaces (max_n_tiles_reg is still registered exactly one cycle
// behind n_tiles_masked -- no FSM/latency change, purely a
// combinational-depth reduction: log2(N_SLOTS) levels instead of
// N_SLOTS).
//
// Written as explicit, uniquely-named per-level wires (NOT a
// multi-dimensional generate-indexed array) -- a first attempt
// using a shared 2D `wire max_tree[level][idx]` array triggered a
// real simulator UNOPTFLAT "circular combinational logic" warning.
// The actual dependency graph IS acyclic (level L+1 only ever
// reads level L), but that tool's array-flattening circularity
// check could not prove that for a shared 2D array; distinctly-
// named per-level wires sidestep the ambiguity entirely for both
// simulation and synthesis. N_SLOTS is a power of two for every
// real configuration this project uses (1/2/4/8); anything else
// falls back, explicitly, to the original flat scan (correct but not
// optimized) rather than silently doing the wrong thing.
reg [15:0] max_n_tiles_reg;
generate
if (N_SLOTS == 1) begin : GEN_MAXTREE_N1
always @(posedge clk) begin
if (rst) max_n_tiles_reg <= 16'h0;
else max_n_tiles_reg <= n_tiles_masked[0];
end
end else if (N_SLOTS == 2) begin : GEN_MAXTREE_N2
wire [15:0] max_final = (n_tiles_masked[0] > n_tiles_masked[1]) ? n_tiles_masked[0] : n_tiles_masked[1];
always @(posedge clk) begin
if (rst) max_n_tiles_reg <= 16'h0;
else max_n_tiles_reg <= max_final;
end
end else if (N_SLOTS == 4) begin : GEN_MAXTREE_N4
wire [15:0] m0 = (n_tiles_masked[0] > n_tiles_masked[1]) ? n_tiles_masked[0] : n_tiles_masked[1];
wire [15:0] m1 = (n_tiles_masked[2] > n_tiles_masked[3]) ? n_tiles_masked[2] : n_tiles_masked[3];
wire [15:0] max_final = (m0 > m1) ? m0 : m1;
always @(posedge clk) begin
if (rst) max_n_tiles_reg <= 16'h0;
else max_n_tiles_reg <= max_final;
end
end else if (N_SLOTS == 8) begin : GEN_MAXTREE_N8
wire [15:0] m0 = (n_tiles_masked[0] > n_tiles_masked[1]) ? n_tiles_masked[0] : n_tiles_masked[1];
wire [15:0] m1 = (n_tiles_masked[2] > n_tiles_masked[3]) ? n_tiles_masked[2] : n_tiles_masked[3];
wire [15:0] m2 = (n_tiles_masked[4] > n_tiles_masked[5]) ? n_tiles_masked[4] : n_tiles_masked[5];
wire [15:0] m3 = (n_tiles_masked[6] > n_tiles_masked[7]) ? n_tiles_masked[6] : n_tiles_masked[7];
wire [15:0] m01 = (m0 > m1) ? m0 : m1;
wire [15:0] m23 = (m2 > m3) ? m2 : m3;
wire [15:0] max_final = (m01 > m23) ? m01 : m23;
always @(posedge clk) begin
if (rst) max_n_tiles_reg <= 16'h0;
else max_n_tiles_reg <= max_final;
end
end else if (N_SLOTS == 16) begin : GEN_MAXTREE_N16
// EXP-0056: same balanced-tree pattern as N_SLOTS==8 above,
// one more level. This is the exact case that used to fall
// through to GEN_MAXTREE_FALLBACK's own flat sequential
// scan -- the real critical path measured blocking
// N_SLOTS=16 timing closure on the LFE5U-85F (worst
// 23.52-24.64MHz vs 64MHz target across two independent
// seeds, both pre-fix; see experiments.log EXP-0056).
wire [15:0] m0 = (n_tiles_masked[0] > n_tiles_masked[1]) ? n_tiles_masked[0] : n_tiles_masked[1];
wire [15:0] m1 = (n_tiles_masked[2] > n_tiles_masked[3]) ? n_tiles_masked[2] : n_tiles_masked[3];
wire [15:0] m2 = (n_tiles_masked[4] > n_tiles_masked[5]) ? n_tiles_masked[4] : n_tiles_masked[5];
wire [15:0] m3 = (n_tiles_masked[6] > n_tiles_masked[7]) ? n_tiles_masked[6] : n_tiles_masked[7];
wire [15:0] m4 = (n_tiles_masked[8] > n_tiles_masked[9]) ? n_tiles_masked[8] : n_tiles_masked[9];
wire [15:0] m5 = (n_tiles_masked[10] > n_tiles_masked[11]) ? n_tiles_masked[10] : n_tiles_masked[11];
wire [15:0] m6 = (n_tiles_masked[12] > n_tiles_masked[13]) ? n_tiles_masked[12] : n_tiles_masked[13];
wire [15:0] m7 = (n_tiles_masked[14] > n_tiles_masked[15]) ? n_tiles_masked[14] : n_tiles_masked[15];
wire [15:0] m01 = (m0 > m1) ? m0 : m1;
wire [15:0] m23 = (m2 > m3) ? m2 : m3;
wire [15:0] m45 = (m4 > m5) ? m4 : m5;
wire [15:0] m67 = (m6 > m7) ? m6 : m7;
wire [15:0] m0123 = (m01 > m23) ? m01 : m23;
wire [15:0] m4567 = (m45 > m67) ? m45 : m67;
wire [15:0] max_final = (m0123 > m4567) ? m0123 : m4567;
always @(posedge clk) begin
if (rst) max_n_tiles_reg <= 16'h0;
else max_n_tiles_reg <= max_final;
end
end else begin : GEN_MAXTREE_FALLBACK
reg [15:0] max_n_tiles_comb_fallback;
integer j;
always @* begin
max_n_tiles_comb_fallback = 16'h0;
for (j = 0; j < N_SLOTS; j = j + 1)
if (n_tiles_masked[j] > max_n_tiles_comb_fallback)
max_n_tiles_comb_fallback = n_tiles_masked[j];
end
always @(posedge clk) begin
if (rst) max_n_tiles_reg <= 16'h0;
else max_n_tiles_reg <= max_n_tiles_comb_fallback;
end
end
endgenerate
localparam ST_IDLE = 1'd0;
localparam ST_FETCH = 1'd1;
reg state;
reg pf_start;
reg [ADDR_WIDTH-1:0] pf_addr;
wire pf_busy, pf_done;
wire signed [DATA_WIDTH*P_IN-1:0] pf_tile;
prefetch_engine #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH)
) u_pf (
.clk(clk), .rst(rst),
.fetch_start(pf_start), .w_addr(pf_addr),
.fetch_busy(pf_busy), .fetch_done(pf_done), .tile_w(pf_tile),
.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)
);
reg fill_we_reg;
reg [TIW-1:0] fill_addr_reg;
reg [DATA_WIDTH*P_IN-1:0] fill_data_reg;
assign fill_we = fill_we_reg;
assign fill_addr = fill_addr_reg;
assign fill_data = fill_data_reg;
always @(posedge clk) begin
if (rst) begin
resident_tag <= {ADDR_WIDTH{1'b1}}; // sentinel: matches no real x_base at reset
resident_count <= {CNTW{1'b0}};
state <= ST_IDLE;
pf_start <= 1'b0;
fill_we_reg <= 1'b0;
end else begin
pf_start <= 1'b0;
fill_we_reg <= 1'b0;
// latch a completed fetch into the replicated activation
// memory's broadcast fill port
if (pf_done) begin
fill_we_reg <= 1'b1;
fill_addr_reg <= resident_count[TIW-1:0]; // valid: gated < max_n_tiles <= MAX_TILES
fill_data_reg <= pf_tile;
resident_count <= resident_count + 1'b1;
end
// refill trigger: the reference slot wants a DIFFERENT tag,
// and the fetch engine is genuinely idle (never interrupt an
// in-flight fetch -- same discipline as memory_manager.v's
// own pf_pending guard, ERR-0006). Stays in ST_IDLE (not
// ST_FETCH): only updates resident_tag/resident_count here;
// the ST_IDLE case below is what actually issues pf_start,
// reading the NEW resident_tag starting next cycle -- this
// path must NOT itself jump to ST_FETCH without a matching
// pf_start, or the engine would sit in ST_FETCH forever
// waiting for a pf_done that was never triggered.
if (desired_valid && (desired_x_base != resident_tag) && !pf_busy && (state == ST_IDLE)) begin
resident_tag <= desired_x_base;
resident_count <= {CNTW{1'b0}};
end
case (state)
ST_IDLE: begin
// stay idle: fetching further tiles for the CURRENT
// tag (if any active slot still needs more) is
// handled below, symmetric to the refill case.
if (!pf_busy && !pf_start && (resident_count < max_n_tiles_reg) &&
desired_valid && (desired_x_base == resident_tag)) begin
pf_start <= 1'b1;
pf_addr <= resident_tag + (resident_count * P_IN[ADDR_WIDTH-1:0]);
state <= ST_FETCH;
end
end
ST_FETCH: begin
if (pf_done) begin
// resident_count already bumped above this cycle;
// decide whether more remain once back in IDLE.
state <= ST_IDLE;
end
end
default: state <= ST_IDLE;
endcase
end
end
endmodule
@@ -0,0 +1,325 @@
`timescale 1ns/1ps
// ================================================================
// Neural Memory System (NMS) -- STEP8 full integration, mirrors
// hardware/v2/rtl/dataflow_core.v's own scope exactly (M6 Dependency
// Manager -> M5 Neural Director -> N_SLOTS x (memory manager + neural
// processor)), but replaces the M4 memory_manager.v +
// activation_cache.v cluster with the NMS's own decided pieces
// (DEC-0019/DEC-0020):
// - nms_activation_replicated.v: N_SLOTS private full-vector
// activation copies, broadcast-filled by...
// - nms_activation_fill_ctrl.v: the shared dedup/fetch controller
// (single logical tag, same honest thrash-under-interleaved-
// different-x_base limitation as the superseded activation_cache.v)
// - nms_weight_packed.v: N_SLOTS private, per-MAC-lane packed weight
// copies (never shared, no arbitration needed)
// - nms_memory_manager.v: per-slot job FSM, reads directly from the
// two SRAMs above instead of double-buffering 2 banks (the whole
// vector is resident, not just 2 tiles worth)
//
// hardware/v2/rtl/dependency_manager.v and neural_director.v are
// REUSED VERBATIM, unmodified -- the node-registration and slot-
// dispatch protocol did not change at all; only what happens between
// "job dispatched to a slot" and "job_done" changed.
//
// Memory Backend Interface: exposed N_SLOTS+1 wide exactly like
// dataflow_core.v (indices [0,N_SLOTS) = per-slot memory managers'
// own weight-fetch+result-write port, index [N_SLOTS] = the shared
// activation fill controller's own port) -- arbitrated one level up,
// reusing hardware/v2/rtl/slot_mem_arbiter.v unchanged.
//
// STEP16 Phase 5: forked from nms_dataflow_core_dual32.v (STEP15's
// own dual-chip-32-bit variant) with ONLY the wide weight-fetch port
// widened from 32 to 64 bits (MEM_DATA_WIDTH=64 in the per-slot
// nms_memory_manager_stream_wide instance below) -- the natural
// P_IN*DATA_WIDTH/16=4-word SDRAM burst identified in Phase 1 means
// ONE mem_req now fetches exactly one whole tile (WORDS_PER_TILE=1),
// same as the dual32 case fetched one whole tile per 32-bit request.
// Everything else (activation path, dependency manager, neural
// director, per-slot neural_processor) is BYTE-FOR-BYTE UNCHANGED --
// per the governing spec's own "do not create an artificial
// benchmark" instruction, only the piece under test (weight-fetch
// physical memory) differs from the validated dual32 baseline.
// ================================================================
module nms_dataflow_core_sdram_fast #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ACC_WIDTH = 32,
parameter ADDR_WIDTH = 26,
parameter N_SLOTS = 4,
parameter N_NODES = 16,
parameter MAX_DEPS = 4,
parameter QUEUE_DEPTH = 8,
parameter MAX_TILES = 16,
parameter PREFETCH_DISTANCE = 8,
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
// Must match nms_memory_manager.v's/nms_activation_fill_ctrl.v's
// own CNTW exactly -- this top-level wire connecting the two was
// left at the narrower TIW after those modules were widened,
// silently truncating resident_count's real value (16) back down
// to 0 right when it should have reached MAX_TILES, deadlocking
// the very last tile of any n_tiles==MAX_TILES job forever (found
// via simulation: D-Stress's real 16-tile neurons hung 1 tile
// short, act_resident_count visibly reset to 0 the exact cycle it
// should have become 16).
parameter CNTW = $clog2(MAX_TILES+1)
)(
input wire clk,
input wire rst,
input wire reg_valid,
output wire reg_ready,
input wire [$clog2(N_NODES)-1:0] reg_node_id,
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
input wire [ADDR_WIDTH-1:0] reg_x_base,
input wire [ADDR_WIDTH-1:0] reg_w_base,
input wire [15:0] reg_n_tiles,
input wire [ADDR_WIDTH-1:0] reg_result_addr,
// FPGA_DATA_READY: see the assignment site (below u_director) for
// the full design comment.
output wire data_ready,
output wire [N_SLOTS:0] slot_mem_req,
output wire [N_SLOTS:0] slot_mem_wr,
output wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr,
output wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata,
output wire [N_SLOTS:0] slot_mem_lb_n,
output wire [N_SLOTS:0] slot_mem_ub_n,
input wire [16*(N_SLOTS+1)-1:0] slot_mem_rdata,
input wire [N_SLOTS:0] slot_mem_ready,
// ---- STEP16 Phase 5: wide (64-bit logical) weight-fetch backend
// interface, per slot -- N_SLOTS wide (NOT N_SLOTS+1: the shared
// activation-fill controller stays on the ORIGINAL 16-bit port
// above, unchanged). Arbitrated one level up (slot_mem_arbiter_
// wide.v, reused unchanged at DATA_WIDTH=64) down to the real
// SDRAM physical interface (sdram_weight_backend.v). ----
output wire [N_SLOTS-1:0] wide_slot_mem_req,
output wire [ADDR_WIDTH*N_SLOTS-1:0] wide_slot_mem_addr,
input wire [64*N_SLOTS-1:0] wide_slot_mem_rdata,
input wire [N_SLOTS-1:0] wide_slot_mem_ready
);
localparam NODE_IDW = $clog2(N_NODES);
wire dm_ready_valid;
wire dm_ready_ready;
wire [NODE_IDW-1:0] dm_ready_node_id;
wire [ADDR_WIDTH-1:0] dm_ready_x_base, dm_ready_w_base, dm_ready_result_addr;
wire [15:0] dm_ready_n_tiles;
wire dm_producer_done_valid;
wire [NODE_IDW-1:0] dm_producer_done_node_id;
wire dm_any_pending;
dependency_manager_fast #(
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
) u_dep_mgr (
.clk(clk), .rst(rst),
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
.reg_result_addr(reg_result_addr),
.producer_done_valid(dm_producer_done_valid), .producer_done_node_id(dm_producer_done_node_id),
.ready_valid(dm_ready_valid), .ready_ready(dm_ready_ready), .ready_node_id(dm_ready_node_id),
.ready_x_base(dm_ready_x_base), .ready_w_base(dm_ready_w_base),
.ready_n_tiles(dm_ready_n_tiles), .ready_result_addr(dm_ready_result_addr),
.any_pending(dm_any_pending)
);
wire [15:0] dm_ready_node_id_ext = {{(16-NODE_IDW){1'b0}}, dm_ready_node_id};
wire [N_SLOTS-1:0] dir_slot_job_start;
wire [ADDR_WIDTH*N_SLOTS-1:0] dir_slot_x_base, dir_slot_w_base, dir_slot_result_addr;
wire [16*N_SLOTS-1:0] dir_slot_n_tiles, dir_slot_node_id;
wire [N_SLOTS-1:0] dir_slot_job_done;
wire dir_job_out_done;
wire [$clog2(N_SLOTS)-1:0] dir_job_out_slot;
wire [3:0] dir_state;
wire dir_error;
wire dir_queue_empty;
neural_director #(
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
) u_director (
.clk(clk), .rst(rst),
.job_in_valid(dm_ready_valid), .job_in_ready(dm_ready_ready),
.job_in_x_base(dm_ready_x_base), .job_in_w_base(dm_ready_w_base),
.job_in_n_tiles(dm_ready_n_tiles), .job_in_result_addr(dm_ready_result_addr),
.job_in_node_id(dm_ready_node_id_ext),
.slot_job_start(dir_slot_job_start), .slot_x_base(dir_slot_x_base), .slot_w_base(dir_slot_w_base),
.slot_n_tiles(dir_slot_n_tiles), .slot_result_addr(dir_slot_result_addr),
.slot_node_id(dir_slot_node_id), .slot_job_done(dir_slot_job_done),
.job_out_done(dir_job_out_done), .job_out_slot(dir_job_out_slot),
.dir_state(dir_state), .dir_error(dir_error), .queue_empty(dir_queue_empty)
);
// ---- FPGA_DATA_READY: system-idle detection (see decisions.log
// for the full design rationale) ----
// sys_busy: true while ANY of {a slot is active, the director's
// dispatch queue is non-empty, dependency_manager has a node not
// yet dispatched} holds. data_ready is a sticky level that goes
// HIGH on the busy->idle falling edge (a graph just finished) and
// LOW again the instant any new work starts (registration or
// dispatch) -- self-clearing, no explicit host ACK needed. Correct
// ONLY if the host finishes registering every node of a graph
// before the first one completes (documented assumption, see
// decisions.log) -- registration (microseconds over SPI) is far
// faster than per-neuron compute (~195 real measured cycles) for
// every workload this project has characterized.
wire sys_busy = (|job_active) || (!dir_queue_empty) || dm_any_pending;
reg sys_busy_prev;
reg data_ready_reg;
always @(posedge clk) begin
if (rst) begin
sys_busy_prev <= 1'b0;
data_ready_reg <= 1'b0;
end else begin
sys_busy_prev <= sys_busy;
if (sys_busy) data_ready_reg <= 1'b0;
else if (sys_busy_prev) data_ready_reg <= 1'b1;
end
end
assign data_ready = data_ready_reg;
wire [15:0] completed_node_id_16 = dir_slot_node_id[dir_job_out_slot*16 +: 16];
assign dm_producer_done_valid = dir_job_out_done;
assign dm_producer_done_node_id = completed_node_id_16[NODE_IDW-1:0];
// ---- NMS memory: shared Activation SRAM (replicated) + private
// Weight SRAM (packed), per DEC-0019/DEC-0020 ----
wire fill_we;
wire [TIW-1:0] fill_addr;
wire signed [DATA_WIDTH*P_IN-1:0] fill_data;
wire [ADDR_WIDTH-1:0] act_resident_tag;
wire [CNTW-1:0] act_resident_count;
wire [N_SLOTS-1:0] act_rd_en;
wire [N_SLOTS*TIW-1:0] act_rd_addr_flat;
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] act_rd_data_flat;
nms_activation_replicated #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
) u_act_mem (
.clk(clk), .rst(rst),
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
.rd_en(act_rd_en), .rd_addr_flat(act_rd_addr_flat), .rd_data_flat(act_rd_data_flat)
);
wire [N_SLOTS-1:0] job_active;
wire [ADDR_WIDTH*N_SLOTS-1:0] job_x_base_flat;
wire [16*N_SLOTS-1:0] job_n_tiles_flat;
nms_activation_fill_ctrl_v3_n16 #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES)
) u_act_fill (
.clk(clk), .rst(rst),
.job_active(job_active), .x_base_flat(job_x_base_flat), .n_tiles_flat(job_n_tiles_flat),
.resident_tag(act_resident_tag), .resident_count(act_resident_count),
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
.mem_req(slot_mem_req[N_SLOTS]), .mem_wr(slot_mem_wr[N_SLOTS]),
.mem_addr(slot_mem_addr[N_SLOTS*ADDR_WIDTH +: ADDR_WIDTH]),
.mem_wdata(slot_mem_wdata[N_SLOTS*16 +: 16]),
.mem_lb_n(slot_mem_lb_n[N_SLOTS]), .mem_ub_n(slot_mem_ub_n[N_SLOTS]),
.mem_rdata(slot_mem_rdata[N_SLOTS*16 +: 16]), .mem_ready(slot_mem_ready[N_SLOTS])
);
wire [N_SLOTS-1:0] wgt_fill_we;
wire [N_SLOTS*TIW-1:0] wgt_fill_addr_flat;
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_fill_data_flat;
wire [N_SLOTS-1:0] wgt_rd_en;
wire [N_SLOTS*TIW-1:0] wgt_rd_addr_flat;
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_rd_data_flat;
nms_weight_packed #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
) u_wgt_mem (
.clk(clk), .rst(rst),
.fill_we(wgt_fill_we), .fill_addr_flat(wgt_fill_addr_flat), .fill_data_flat(wgt_fill_data_flat),
.rd_en(wgt_rd_en), .rd_addr_flat(wgt_rd_addr_flat), .rd_data_flat(wgt_rd_data_flat)
);
genvar g;
generate
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_SLOT
wire mm_operand_valid, mm_operand_ready;
wire signed [DATA_WIDTH*P_IN-1:0] mm_input_data, mm_weight_data;
wire mm_tile_last;
wire mm_result_valid, mm_result_ready;
wire signed [DATA_WIDTH-1:0] mm_result_data;
nms_memory_manager_stream_wide #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES),
.PREFETCH_DISTANCE(PREFETCH_DISTANCE), .MEM_DATA_WIDTH(64)
) u_mm (
.clk(clk), .rst(rst),
.job_start(dir_slot_job_start[g]),
.x_base(dir_slot_x_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
.w_base(dir_slot_w_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
.n_tiles(dir_slot_n_tiles[g*16 +: 16]),
.result_addr(dir_slot_result_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
.job_done(dir_slot_job_done[g]),
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
.result_valid(mm_result_valid), .result_ready(mm_result_ready), .result_data(mm_result_data),
.job_active(job_active[g]),
.job_x_base(job_x_base_flat[g*ADDR_WIDTH +: ADDR_WIDTH]),
.job_n_tiles(job_n_tiles_flat[g*16 +: 16]),
.act_resident_tag(act_resident_tag), .act_resident_count(act_resident_count),
.act_rd_en(act_rd_en[g]),
.act_rd_addr(act_rd_addr_flat[g*TIW +: TIW]),
.act_rd_data(act_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
.wgt_fill_we(wgt_fill_we[g]),
.wgt_fill_addr(wgt_fill_addr_flat[g*TIW +: TIW]),
.wgt_fill_data(wgt_fill_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
.wgt_rd_en(wgt_rd_en[g]),
.wgt_rd_addr(wgt_rd_addr_flat[g*TIW +: TIW]),
.wgt_rd_data(wgt_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
.mem_req(slot_mem_req[g]), .mem_wr(slot_mem_wr[g]),
.mem_addr(slot_mem_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
.mem_wdata(slot_mem_wdata[g*16 +: 16]),
.mem_lb_n(slot_mem_lb_n[g]), .mem_ub_n(slot_mem_ub_n[g]),
.mem_rdata(slot_mem_rdata[g*16 +: 16]), .mem_ready(slot_mem_ready[g]),
.wide_mem_req(wide_slot_mem_req[g]),
.wide_mem_addr(wide_slot_mem_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
.wide_mem_rdata(wide_slot_mem_rdata[g*64 +: 64]),
.wide_mem_ready(wide_slot_mem_ready[g])
);
reg job_valid_np;
wire job_ready_np;
wire result_valid_np;
wire signed [DATA_WIDTH-1:0] result_data_np;
wire [3:0] np_state;
wire np_error;
neural_processor #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
) u_np (
.clk(clk), .rst(rst),
.job_valid(job_valid_np), .job_ready(job_ready_np),
.job_node_id(16'h0), .job_bias(8'sd0), .job_activation(2'd1),
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
.result_valid(result_valid_np), .result_ready(mm_result_ready),
.result_data(result_data_np), .result_node_id(),
.np_state(np_state), .np_error(np_error)
);
assign mm_result_valid = result_valid_np;
assign mm_result_data = result_data_np;
always @(posedge clk) begin
if (rst) job_valid_np <= 1'b0;
else if (dir_slot_job_start[g]) job_valid_np <= 1'b1;
else if (job_valid_np && job_ready_np) job_valid_np <= 1'b0;
end
end
endgenerate
endmodule
@@ -0,0 +1,171 @@
`timescale 1ns/1ps
// ================================================================
// Neural Memory System (NMS) -- STEP19 real hardware-facing top level.
//
// SINGLE EXTERNAL SDRAM ONLY. Forked from nms_neural_multiprocessor_
// sdram_pack128.v (STEP18) with the ONE change this step's own
// governing spec mandates: the real hardware/v1/rtl/psram_controller.v
// + memory_interface.v pairing (activation-fill + result-writeback,
// 16-bit) is REMOVED from the V2 physical path entirely and replaced
// by sdram_unified_backend.v's own AR port, sharing the SAME single
// physical AS4C4M16SA-6TIN SDRAM chip and the SAME single sdram_
// controller.v instance the weight-fetch path (W port) already uses.
//
// slot_mem_arbiter.v (16-bit, activation+result) and slot_mem_
// arbiter_wide.v (64-bit, weight) are BOTH reused completely
// UNCHANGED -- their own downstream ports now both terminate at
// sdram_unified_backend.v instead of two separate physical chains.
// nms_dataflow_core_sdram.v, nms_activation_fill_ctrl_v3.v, nms_
// memory_manager_stream_wide.v, weight_prefetch_engine_wide.v, and
// neural_processor.v are ALL byte-for-byte unchanged -- this is a
// pure memory-side substitution, per the governing spec's own
// explicit instruction.
//
// V1 (hardware/v1/**) is untouched -- psram_controller.v and memory_
// interface.v simply are no longer INSTANTIATED by this top-level;
// neither file was modified, and V1's own golden-reference status is
// unaffected.
//
// Real pin count (weight+activation+result, ALL through ONE chip):
// 2(BA)+12(A)+1(CKE)+1(CS#)+1(RAS#)+1(CAS#)+1(WE#)+2(DQM)+16(DQ) = 37
// pins total -- the SAME 37 pins the weight-only path already used in
// STEP16-18 (no NEW physical SDRAM pins are needed to add activation/
// result traffic, since it shares the identical physical bus).
// ================================================================
module nms_neural_multiprocessor_sdram_openrow_fast #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ACC_WIDTH = 32,
parameter ADDR_WIDTH = 26,
parameter N_SLOTS = 2,
parameter N_NODES = 16,
parameter MAX_DEPS = 4,
parameter QUEUE_DEPTH = 8,
parameter MAX_TILES = 16,
parameter PREFETCH_DISTANCE = 8,
parameter CLK_FREQ_MHZ = 80
)(
input wire clk,
input wire rst,
input wire reg_valid,
output wire reg_ready,
input wire [$clog2(N_NODES)-1:0] reg_node_id,
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
input wire [ADDR_WIDTH-1:0] reg_x_base,
input wire [ADDR_WIDTH-1:0] reg_w_base,
input wire [15:0] reg_n_tiles,
input wire [ADDR_WIDTH-1:0] reg_result_addr,
// FPGA_DATA_READY: system-idle sticky flag, see nms_dataflow_core_sdram.v
output wire data_ready,
// ---- STEP19: ONE physical SDRAM interface, ALL traffic
// (weights + activations + results) ----
output wire sdram_cke,
output wire sdram_cs_n,
output wire sdram_ras_n,
output wire sdram_cas_n,
output wire sdram_we_n,
output wire [1:0] sdram_ba,
output wire [12:0] sdram_a,
inout wire [15:0] sdram_dq,
output wire [1:0] sdram_dqm
);
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
wire [N_SLOTS:0] slot_mem_ready;
wire [N_SLOTS-1:0] wide_slot_mem_req;
wire [ADDR_WIDTH*N_SLOTS-1:0] wide_slot_mem_addr;
wire [64*N_SLOTS-1:0] wide_slot_mem_rdata;
wire [N_SLOTS-1:0] wide_slot_mem_ready;
nms_dataflow_core_sdram_fast #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
) u_dataflow_core (
.clk(clk), .rst(rst),
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
.reg_result_addr(reg_result_addr),
.data_ready(data_ready),
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready),
.wide_slot_mem_req(wide_slot_mem_req), .wide_slot_mem_addr(wide_slot_mem_addr),
.wide_slot_mem_rdata(wide_slot_mem_rdata), .wide_slot_mem_ready(wide_slot_mem_ready)
);
// ---- AR: activation-fill (shared, 1 port) + per-slot result
// writeback (N_SLOTS ports), arbitrated exactly as before ----
wire arb_m_req, arb_m_wr;
wire [ADDR_WIDTH-1:0] arb_m_addr;
wire [15:0] arb_m_wdata;
wire arb_m_lb_n, arb_m_ub_n;
wire [15:0] arb_m_rdata;
wire arb_m_ready;
slot_mem_arbiter #(
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
) u_arbiter (
.clk(clk), .rst(rst),
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
);
// ---- W: weight fetch (N_SLOTS ports), arbitrated exactly as
// before -- weight fetch never writes, same tie-off convention
// as STEP16-18 ----
wire [N_SLOTS-1:0] wide_s_wr = {N_SLOTS{1'b0}};
wire [64*N_SLOTS-1:0] wide_s_wdata = {(64*N_SLOTS){1'b0}};
wire [N_SLOTS-1:0] wide_s_lb_n = {N_SLOTS{1'b0}};
wire [N_SLOTS-1:0] wide_s_ub_n = {N_SLOTS{1'b0}};
wire wide_arb_m_req, wide_arb_m_wr;
wire [ADDR_WIDTH-1:0] wide_arb_m_addr;
wire [63:0] wide_arb_m_wdata;
wire wide_arb_m_lb_n, wide_arb_m_ub_n;
wire [63:0] wide_arb_m_rdata;
wire wide_arb_m_ready;
slot_mem_arbiter_wide #(
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS), .DATA_WIDTH(64)
) u_arbiter_wide (
.clk(clk), .rst(rst),
.s_req(wide_slot_mem_req), .s_wr(wide_s_wr), .s_addr(wide_slot_mem_addr),
.s_wdata(wide_s_wdata), .s_lb_n(wide_s_lb_n), .s_ub_n(wide_s_ub_n),
.s_rdata(wide_slot_mem_rdata), .s_ready(wide_slot_mem_ready),
.m_req(wide_arb_m_req), .m_wr(wide_arb_m_wr), .m_addr(wide_arb_m_addr), .m_wdata(wide_arb_m_wdata),
.m_lb_n(wide_arb_m_lb_n), .m_ub_n(wide_arb_m_ub_n),
.m_rdata(wide_arb_m_rdata), .m_ready(wide_arb_m_ready)
);
// ---- STEP19: ONE physical SDRAM backend, both W and AR ports ----
sdram_unified_backend_openrow #(
.ADDR_WIDTH(ADDR_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
) u_sdram_backend (
.clk(clk), .rst(rst),
.w_req(wide_arb_m_req), .w_addr(wide_arb_m_addr),
.w_rdata(wide_arb_m_rdata), .w_ready(wide_arb_m_ready),
.ar_req(arb_m_req), .ar_wr(arb_m_wr), .ar_addr(arb_m_addr), .ar_wdata(arb_m_wdata),
.ar_lb_n(arb_m_lb_n), .ar_ub_n(arb_m_ub_n),
.ar_rdata(arb_m_rdata), .ar_ready(arb_m_ready),
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
);
endmodule
@@ -0,0 +1,281 @@
`timescale 1ns/1ps
// ================================================================
// FPGA-Neural V2 -- board-level top INTEGRATION SMOKE TEST (STEP20)
//
// Proves the NEW STEP20 wiring end-to-end: real SPI transactions (bit-
// banged, mode 0) drive job registration THROUGH spi_host_bridge.v,
// through the real compute+memory pipeline (byte-for-byte identical
// to the already-verified STEP19 nms_neural_multiprocessor_sdram_
// unified.v internals) via the NEW 2-level host-arb AR arbitration,
// down to the SAME single sdram_unified_backend/sdram_controller/
// AS4C4M16SA-6TIN chain -- checked against a real, backdoor-peeked
// SDRAM result. This is NOT a replacement for the STEP19 full 256-
// neuron D-Stress regression (already reconfirmed bit-exact using the
// trusted tool, see errors.log ERR-0024) -- it exists purely to
// validate the NEW pieces this step adds (SPI bridge, PLL-bypass
// clocking, reset_sync, the extra host-arb arbiter level) that
// D-Stress's own tight, back-to-back dispatch loop never exercises:
// realistic, WIDELY TIME-SEPARATED job pacing, as a real host would
// actually issue over SPI.
//
// STATUS (STEP20, ERR-0025 Part B): FIXED. Root cause: nms_weight_
// packed.v / nms_activation_replicated.v used a REGISTERED read (one
// full extra clock of latency) while nms_memory_manager_stream_wide.v's
// own read-ahead pipeline (`rd_pending`) assumes a COMBINATIONAL read
// (issue this cycle, data valid to capture next cycle). A busy multi-
// tile job's own prefetch lead time always absorbs the extra cycle
// invisibly; an uncontested single-tile job's first (only) tile has
// zero such margin and captured stale/zero data permanently. Fixed by
// making both SRAMs' reads combinational (with an explicit same-cycle
// fill/read bypass for the one hazard a combinational read alone would
// still miss). Verified: this test now passes, AND the STEP19 D-Stress
// regression (N=2 49788 cycles, N=4 49771 cycles, both 256/256
// bit-exact) is UNCHANGED -- cycle-for-cycle identical to before the
// fix, since D-Stress's own prefetch margin never depended on the
// extra (buggy) register cycle in the first place.
//
// Six scenarios below, using disjoint SDRAM regions so none interfere:
// A) two jobs, realistic wide SPI pacing (the original failing case)
// B) a single job dispatched alone (twice: neuron0 alone, neuron1 alone)
// C) two jobs back-to-back (minimal CS gap)
// D) two jobs with a large gap (same as A, kept as its own named case)
// G) parametric sweep across several distinct inter-job gaps, proving
// the fix does not depend on any particular cycle count
//
// Weights/activations are preloaded via the same backdoor poke
// convention already used by tb_nms_dstress_sdram_unified.v (direct
// writes into u_sdram.mem[]) -- only JOB REGISTRATION goes through the
// real, physical SPI path, since that is the actual integration
// surface under test. `SIM bypasses the (unsimulatable) EHXPLLL
// primitive inside ecp5_pll_sys_clk.v with a direct pass-through, per
// that module's own documented, declared limitation.
// ================================================================
`define SIM
module tb_fpga_neural_v2_top_smoke;
localparam ADDR_WIDTH = 26; // AS4C32M16SA memory upgrade
localparam N_SLOTS = 2;
localparam N_NODES = 16;
localparam MAX_DEPS = 4;
reg osc_clk = 0;
// Driven at the REAL 64MHz clk_sys rate (not the board's own 16MHz
// osc_clk) -- under the `SIM PLL bypass (clk_sys = osc_clk
// directly, see ecp5_pll_sys_clk.v), this reproduces the real
// board's actual system-clock rate for this test, matching
// CLK_FREQ_MHZ(64) above (a previous draft left both this and the
// controller's own CLK_FREQ_MHZ at a stale, pre-freeze value).
always #7.8125 osc_clk = ~osc_clk; // 64MHz
reg ext_rst_n = 0;
reg spi_sclk = 0, spi_mosi = 0, spi_cs_n = 1;
wire spi_miso;
wire sdram_cke, sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n;
wire [1:0] sdram_ba;
wire [12:0] sdram_a;
wire [15:0] sdram_dq;
wire [1:0] sdram_dqm;
wire pll_locked;
fpga_neural_v2_top_openrow_fast #(
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS),
.CLK_FREQ_MHZ(64)
) dut (
.osc_clk(osc_clk), .ext_rst_n(ext_rst_n),
.spi_sclk(spi_sclk), .spi_mosi(spi_mosi), .spi_miso(spi_miso), .spi_cs_n(spi_cs_n),
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm),
.pll_locked(pll_locked)
);
sdram_model #(.CLK_FREQ_MHZ(64)) u_sdram (
.clk(dut.clk_sys), .cke(sdram_cke), .cs_n(sdram_cs_n), .ras_n(sdram_ras_n),
.cas_n(sdram_cas_n), .we_n(sdram_we_n), .ba(sdram_ba), .a(sdram_a),
.dq(sdram_dq), .dqm(sdram_dqm)
);
function automatic signed [7:0] relu_sat(input signed [31:0] acc);
begin
if (acc < 0) relu_sat = 8'sd0;
else if (acc > 127) relu_sat = 8'sd127;
else relu_sat = acc[7:0];
end
endfunction
task poke_byte(input [ADDR_WIDTH-1:0] byte_addr, input signed [7:0] val);
reg [24:0] word_addr;
begin
word_addr = byte_addr[ADDR_WIDTH-1:1];
if (byte_addr[0] == 1'b0) u_sdram.mem[word_addr][7:0] = val;
else u_sdram.mem[word_addr][15:8] = val;
end
endtask
function automatic signed [7:0] peek_byte(input [ADDR_WIDTH-1:0] byte_addr);
reg [24:0] word_addr;
begin
word_addr = byte_addr[ADDR_WIDTH-1:1];
peek_byte = (byte_addr[0] == 1'b0) ? u_sdram.mem[word_addr][7:0] : u_sdram.mem[word_addr][15:8];
end
endfunction
// ---- SPI master BFM (matches spi_host_bridge.v's own protocol,
// same realistic 500ns-bit-period convention as tb_spi_host_
// bridge.v -- see that module's header on the CDC margin reason) ----
task spi_byte(input [7:0] tx, output [7:0] rx);
integer i;
begin
rx = 8'h00;
for (i = 7; i >= 0; i = i - 1) begin
spi_mosi = tx[i];
#200; spi_sclk = 1; #50; rx = {rx[6:0], spi_miso}; #50; spi_sclk = 0; #200;
end
end
endtask
task write_job(input [3:0] node_id, input [2:0] required, input [15:0] producer_ids,
input [ADDR_WIDTH-1:0] x_base, input [ADDR_WIDTH-1:0] w_base, input [15:0] n_tiles,
input [ADDR_WIDTH-1:0] result_addr);
reg [7:0] rxb;
begin
spi_cs_n = 0; #20;
spi_byte(8'h10, rxb);
spi_byte({4'b0, node_id}, rxb);
spi_byte({5'b0, required}, rxb);
spi_byte(producer_ids[15:8], rxb);
spi_byte(producer_ids[7:0], rxb);
spi_byte({6'b0, x_base[25:24]}, rxb);
spi_byte(x_base[23:16], rxb);
spi_byte(x_base[15:8], rxb);
spi_byte(x_base[7:0], rxb);
spi_byte({6'b0, w_base[25:24]}, rxb);
spi_byte(w_base[23:16], rxb);
spi_byte(w_base[15:8], rxb);
spi_byte(w_base[7:0], rxb);
spi_byte(n_tiles[15:8], rxb);
spi_byte(n_tiles[7:0], rxb);
spi_byte({6'b0, result_addr[25:24]}, rxb);
spi_byte(result_addr[23:16], rxb);
spi_byte(result_addr[15:8], rxb);
spi_byte(result_addr[7:0], rxb);
// hold CS through the reg_valid/reg_ready handshake (may
// need a few extra idle clocks if the target slot is busy)
#2000;
spi_cs_n = 1; #200;
end
endtask
integer errors, tests;
integer node_ctr; // fresh node_id per sub-test (dependency_manager never reclaims a dispatched id)
task check_neuron(input [22:0] x_base, input [22:0] w_base, input [22:0] res_addr,
input [255:0] label);
integer k;
reg signed [31:0] acc;
reg signed [7:0] golden, real_y;
begin
acc = 0;
for (k = 0; k < 8; k = k + 1)
acc = acc + peek_byte(x_base + k) * peek_byte(w_base + k);
golden = relu_sat(acc);
real_y = peek_byte(res_addr);
tests = tests + 1;
if (real_y !== golden) begin
errors = errors + 1;
$display("FAIL %0s: real=%0d golden=%0d", label, real_y, golden);
end else begin
$display("PASS %0s: real=%0d golden=%0d", label, real_y, golden);
end
end
endtask
// One independent, disjoint scratch region per pair-test invocation,
// so scenarios never interfere with each other's SDRAM content:
// x_base=region, w0=region+0x100, w1=region+0x110, res=region+0x200/0x201
task run_pair(input [22:0] region, input integer gap_ns, input [255:0] label);
reg [22:0] x_base, w0, w1, res0, res1;
integer k, n;
begin
x_base = region;
w0 = region + 26'h100;
w1 = region + 26'h110;
res0 = region + 26'h200;
res1 = region + 26'h201;
for (k = 0; k < 8; k = k + 1) poke_byte(x_base + k, k[7:0] + 1);
for (n = 0; n < 2; n = n + 1)
for (k = 0; k < 8; k = k + 1)
poke_byte((n == 0 ? w0 : w1) + k, ((n + k) % 4) + 1);
poke_byte(res0, 8'sd0);
poke_byte(res1, 8'sd0);
write_job(node_ctr[3:0], 3'd0, 16'h0000, x_base, w0, 16'd1, res0);
node_ctr = node_ctr + 1;
if (gap_ns > 0) #gap_ns;
write_job(node_ctr[3:0], 3'd0, 16'h0000, x_base, w1, 16'd1, res1);
node_ctr = node_ctr + 1;
repeat (3000) @(posedge dut.clk_sys);
check_neuron(x_base, w0, res0, {label, "-A"});
check_neuron(x_base, w1, res1, {label, "-B"});
end
endtask
// Single, standalone job (scenario B) -- no second job at all.
task run_single(input [22:0] region, input [255:0] label);
reg [22:0] x_base, w0, res0;
integer k;
begin
x_base = region;
w0 = region + 26'h100;
res0 = region + 26'h200;
for (k = 0; k < 8; k = k + 1) poke_byte(x_base + k, k[7:0] + 3);
for (k = 0; k < 8; k = k + 1) poke_byte(w0 + k, ((k) % 3) + 1);
poke_byte(res0, 8'sd0);
write_job(node_ctr[3:0], 3'd0, 16'h0000, x_base, w0, 16'd1, res0);
node_ctr = node_ctr + 1;
repeat (3000) @(posedge dut.clk_sys);
check_neuron(x_base, w0, res0, label);
end
endtask
initial begin
errors = 0; tests = 0; node_ctr = 0;
ext_rst_n = 0;
repeat (20) @(posedge osc_clk);
ext_rst_n = 1;
repeat (10) @(posedge osc_clk);
wait (dut.u_sdram_backend.u_sdram_ctrl.state == dut.u_sdram_backend.u_sdram_ctrl.S_IDLE);
@(posedge dut.clk_sys);
// B) single job, alone
run_single(26'h001000, "B-single-neuron0");
// A/D) two jobs, realistic wide SPI pacing (~85us worth of SPI
// framing plus an explicit extra gap -- the original failing case)
run_pair(26'h004000, 20000, "A-wide-gap");
// C) two jobs back-to-back (minimal CS-high gap between them)
run_pair(26'h007000, 0, "C-back-to-back");
// G) parametric sweep across several distinct inter-job gaps
run_pair(26'h00A000, 100, "G-gap100ns");
run_pair(26'h00D000, 5000, "G-gap5000ns");
run_pair(26'h010000, 50000, "G-gap50000ns");
$display("=== tb_fpga_neural_v2_top_smoke: %0d/%0d PASS ===", tests-errors, tests);
if (errors != 0) $display("*** %0d FAILURES ***", errors);
$finish;
end
endmodule
@@ -0,0 +1,880 @@
`timescale 1ns/1ps
// ================================================================
// FPGA-Neural V2 -- Final Benchmark Campaign (post-M10, real
// end-to-end characterization, docs/v2-description.md §22/§30/§32)
//
// One testbench, compiled once per N_SLOTS configuration (N_SLOTS_CFG
// parameter, overridden at Verilator invocation via -GN_SLOTS_CFG=N),
// running SIX representative workloads back-to-back through the REAL
// neural_multiprocessor.v (M8: dataflow_core + slot_mem_arbiter + the
// real, unmodified V1 PSRAM chain), with:
// - a software "golden" model replicating neural_processor.v's exact
// integer math (sum(x*w) over all tiles, ReLU + INT8 saturate --
// dataflow_core.v hardcodes bias=0/ACT_RELU for every job, so the
// golden model only needs to replicate that one path)
// - bit-exact verification of EVERY neuron's real result against
// that golden model (peek_byte from the real psram_model backing
// array -- an oracle independent of the RTL under test)
// - real cycle-accounting instrumentation (testbench-only, no RTL
// touched): per-slot busy/idle cycles, shared PSRAM port busy/idle
// cycles, REAL tiles delivered per slot (operand_valid&&
// operand_ready pulses -- one pulse = one whole P_IN-wide tile
// consumed by neural_processor, NOT one byte), director/dependency
// bookkeeping (jobs allocated/completed, ready-queue occupancy,
// WAITING/READY/DISPATCHED node counts, producer-done wakeups)
//
// Workloads (node_id ranges are disjoint across all six so the WHOLE
// campaign runs in ONE continuous simulation -- only ONE real PSRAM
// power-up wait, no reset between phases, closer to real sustained
// operation than resetting between every workload):
// A) Small -- 16 independent neurons, 8 inputs each
// B) Medium -- 64 independent neurons, 32 inputs each
// C) Large -- 128 independent neurons, 128 inputs each
// D) Stress -- 256 independent neurons, 128 inputs each
// E) Multilayer -- 8 layer-1 neurons (RANDOM data, logged seed) feed
// a shared 8-byte hidden vector; 2 layer-2 neurons
// consume that vector (real cross-node data
// forwarding through real PSRAM, real dependency
// wake-up, "shared producer/multiple consumers")
// F) DAG -- 6-node diamond+fan-in graph (A,B independent; C
// dep on A; D dep on B; E dep on BOTH C and D
// [2-hop transitive wake-up]; F dep on A,B,C [mixed
// direct+1-hop, 3 producers])
//
// All workloads A-D use a REALISTIC dense-layer shape: one shared
// input activation vector, N independent weight vectors (one per
// neuron) -- exactly how a real fully-connected layer's neurons share
// their layer's input. This is not an isolated synthetic microbench.
//
// Verified with Verilator (decisions.log DEC-0004).
// ================================================================
// ================================================================
// STEP11 variant: identical D-Stress workload/golden-model/correctness
// criteria as tb_nms_dstress.v (STEP9's own official benchmark), but
// instantiating nms_neural_multiprocessor_pf (REAL weight prefetch
// engine, weight_prefetch_engine.v) instead of the baseline
// nms_neural_multiprocessor.v, with an added PFD_CFG (PREFETCH_DISTANCE)
// parameter, plus NEW instrumentation (testbench-only, no RTL touched)
// for the two STEP11-mandated metrics that cannot be derived from the
// STEP9 instrumentation alone:
// weight_stall_cycles = cycles a slot is otherwise ready to
// present a tile (activation resident,
// in bounds) but blocked purely because
// tile_idx >= wgt_ready_count
// prefetch_effectiveness = tiles consumed with ZERO such
// weight-blocking cycles beforehand
// (i.e. the weight was ALREADY resident
// the moment the tile became eligible)
// / total tiles consumed
// per STEP11's own explicit metric definitions.
// ================================================================
module tb #(
parameter N_SLOTS_CFG = 2,
parameter PFD_CFG = 8
);
localparam ADDR_WIDTH = 26; // AS4C32M16SA: 25-bit word address + 1 byte-select bit
localparam DATA_WIDTH = 8;
localparam P_IN = 8;
localparam ACC_WIDTH = 32;
// N_NODES must exceed the HIGHEST node_id used by ANY workload
// (node_base + count - 1) -- workload D's own range alone
// (node_base=400, 256 neurons) reaches id 655. An earlier draft
// used N_NODES=512: D's ids silently wrapped (9-bit truncation)
// past id 511, colliding with workload A's already-DISPATCHED
// node 0 (dependency_manager never reclaims dispatched node slots,
// DEC-0008) and deadlocking register_node's reg_ready wait
// forever. A real consequence of DEC-0008's design choice, not an
// RTL bug -- fixed here by sizing N_NODES generously above the
// real id range used below (see decisions.log DEC-0008 and the
// final benchmark report's Limitations section).
localparam N_NODES = 1024;
localparam MAX_DEPS = 8;
localparam QUEUE_DEPTH = 8;
localparam NODE_IDW = $clog2(N_NODES);
localparam CLK_PERIOD = 12.5; // 80 MHz, matches psram_controller's CLK_FREQ_MHZ
reg clk, rst;
initial begin clk = 1'b0; forever #(CLK_PERIOD/2.0) clk = ~clk; end
reg reg_valid;
wire reg_ready;
reg [NODE_IDW-1:0] reg_node_id;
reg [$clog2(MAX_DEPS+1)-1:0] reg_required;
reg [MAX_DEPS*NODE_IDW-1:0] reg_producer_ids;
reg [ADDR_WIDTH-1:0] reg_x_base, reg_w_base, reg_result_addr;
reg [15:0] reg_n_tiles;
// STEP19: ONE physical SDRAM interface. weights, activations, and
// results ALL share this single bus/chip now -- no PSRAM anywhere.
wire sdram_cke, sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n;
wire [1:0] sdram_ba;
wire [12:0] sdram_a;
wire [15:0] sdram_dq;
wire [1:0] sdram_dqm;
nms_neural_multiprocessor_sdram_openrow_fast #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
.N_SLOTS(N_SLOTS_CFG), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
.MAX_TILES(16), .PREFETCH_DISTANCE(PFD_CFG), .CLK_FREQ_MHZ(80)
) u_nmp (
.clk(clk), .rst(rst),
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
.reg_result_addr(reg_result_addr),
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
);
sdram_model #(.CLK_FREQ_MHZ(80)) u_sdram (
.clk(clk), .cke(sdram_cke), .cs_n(sdram_cs_n), .ras_n(sdram_ras_n),
.cas_n(sdram_cas_n), .we_n(sdram_we_n), .ba(sdram_ba), .a(sdram_a),
.dq(sdram_dq), .dqm(sdram_dqm)
);
// ============================================================
// STEP19: byte-level backdoor access (test setup/verification
// only) -- weights, activations, AND results now ALL live on the
// single real SDRAM physical interface (u_sdram); there is no
// PSRAM anywhere in this system anymore. poke_byte/peek_byte (used
// by activation+result call sites) and poke_byte_weight/peek_byte
// _weight (used by weight call sites) are now identical in
// implementation -- kept as two names rather than merged, to avoid
// touching every one of their many existing call sites for a
// cosmetic rename; both correctly target the same u_sdram.mem
// backing array via the same byte_addr>>1 / byte_addr[0] pattern.
// ============================================================
task automatic poke_byte(input [ADDR_WIDTH-1:0] byte_addr, input signed [7:0] val);
reg [24:0] word_addr;
begin
word_addr = byte_addr[ADDR_WIDTH-1:1];
if (byte_addr[0] == 1'b0) u_sdram.mem[word_addr][7:0] = val;
else u_sdram.mem[word_addr][15:8] = val;
end
endtask
function automatic signed [7:0] peek_byte(input [ADDR_WIDTH-1:0] byte_addr);
reg [24:0] word_addr;
begin
word_addr = byte_addr[ADDR_WIDTH-1:1];
peek_byte = (byte_addr[0] == 1'b0) ? u_sdram.mem[word_addr][7:0] : u_sdram.mem[word_addr][15:8];
end
endfunction
// sdram_model.v's own `mem` array is flat-indexed by the 25-bit
// word address directly (bank*ROWS*COLS + row*COLS + col, which,
// given ROWS=8192/COLS=1024 are both powers of 2, is numerically
// IDENTICAL to treating the address as one flat 25-bit integer --
// confirmed against sdram_model.v's own BANKS/ROWS/COLS localparams
// before writing this, not assumed) -- so this is the exact same
// byte_addr>>1 / byte_addr[0] pattern as the original single-chip
// poke_byte/peek_byte above, just against u_sdram.mem instead of
// u_psram.mem.
task automatic poke_byte_weight(input [ADDR_WIDTH-1:0] byte_addr, input signed [7:0] val);
reg [24:0] word_addr;
begin
word_addr = byte_addr[ADDR_WIDTH-1:1];
if (byte_addr[0] == 1'b0) u_sdram.mem[word_addr][7:0] = val;
else u_sdram.mem[word_addr][15:8] = val;
end
endtask
function automatic signed [7:0] peek_byte_weight(input [ADDR_WIDTH-1:0] byte_addr);
reg [24:0] word_addr;
begin
word_addr = byte_addr[ADDR_WIDTH-1:1];
peek_byte_weight = (byte_addr[0] == 1'b0) ? u_sdram.mem[word_addr][7:0] : u_sdram.mem[word_addr][15:8];
end
endfunction
// Golden model: exactly replicates neural_processor.v's real path
// through dataflow_core (bias=0, ACT_RELU always -- see
// dataflow_core.v's own hardcoded job_bias/job_activation).
function automatic signed [7:0] relu_sat(input integer acc);
begin
if (acc <= 0) relu_sat = 8'sd0;
else if (acc > 127) relu_sat = 8'sd127;
else relu_sat = acc[7:0];
end
endfunction
// ============================================================
// Node registration (generalized to MAX_DEPS=8 producers, passed
// as a packed array; n_producers of them are meaningful, the rest
// ignored since reg_required gates how many entries the RTL
// actually reads).
// ============================================================
task automatic register_node(
input [NODE_IDW-1:0] nid,
input [$clog2(MAX_DEPS+1)-1:0] required,
input [MAX_DEPS*NODE_IDW-1:0] producer_ids_packed,
input [ADDR_WIDTH-1:0] xb, input [ADDR_WIDTH-1:0] wb,
input [15:0] nt, input [ADDR_WIDTH-1:0] resaddr
);
begin
@(posedge clk);
reg_node_id = nid;
reg_required = required;
reg_producer_ids = producer_ids_packed;
reg_x_base = xb; reg_w_base = wb; reg_n_tiles = nt; reg_result_addr = resaddr;
reg_valid = 1'b1;
while (!reg_ready) @(posedge clk);
@(posedge clk);
reg_valid = 1'b0;
end
endtask
// ============================================================
// M10+ real cycle-accounting instrumentation (testbench-only, no
// RTL touched -- same idiom as EXP-0013).
// ============================================================
reg measure_en;
integer total_cycles;
integer psram_busy_cycles;
integer ni; // moved up from its original later declaration point
// (STEP20 tooling-compatibility fix, zero behavior
// change -- see nms_memory_manager_stream_wide.v's own
// header note on icarus 13.0's stricter declared-
// before-use rule for procedural blocks)
genvar gi;
reg [N_SLOTS_CFG-1:0] slot_busy_bit; // memory_manager.state != MM_IDLE, this cycle
reg [N_SLOTS_CFG-1:0] slot_tile_bit; // operand_valid && operand_ready, this cycle
integer slot_busy_cycles [0:N_SLOTS_CFG-1];
integer slot_tiles_delivered [0:N_SLOTS_CFG-1];
generate
for (gi = 0; gi < N_SLOTS_CFG; gi = gi + 1) begin : GEN_SLOT_MON
always @(*) begin
slot_busy_bit[gi] = (u_nmp.u_dataflow_core.GEN_SLOT[gi].u_mm.state != 3'd0);
slot_tile_bit[gi] = u_nmp.u_dataflow_core.GEN_SLOT[gi].mm_operand_valid &&
u_nmp.u_dataflow_core.GEN_SLOT[gi].mm_operand_ready;
end
end
endgenerate
// ============================================================
// STEP17 Part B/C: cycle-decomposition + SDRAM effectiveness
// instrumentation (testbench-only, no RTL touched).
// ============================================================
integer active_count; // popcount(slot_busy_bit) this cycle
integer active_hist [0:4]; // cycles with exactly k active slots, k=0..4
integer useful_mac_cycles; // sum over cycles of (#slots with slot_tile_bit this cycle)
integer first_tile_cyc; // total_cycles value at the first tile ever delivered (startup boundary)
integer last_tile_cyc; // total_cycles value at the most recent tile delivered (drain boundary)
integer any_tile_bit;
// SDRAM controller-port instrumentation (real signals on the
// actual sdram_controller.v instance servicing all weight fetch)
integer sdram_req_count, sdram_ready_count, sdram_wr_count;
integer sdram_busy_cycles, sdram_refresh_count;
integer sdram_req_start_cyc, sdram_lat_sum, sdram_lat_min, sdram_lat_max, sdram_lat_n;
reg sdram_prev_state_is_refwait;
initial begin
active_hist[0]=0; active_hist[1]=0; active_hist[2]=0; active_hist[3]=0; active_hist[4]=0;
useful_mac_cycles = 0; first_tile_cyc = -1; last_tile_cyc = -1;
sdram_req_count=0; sdram_ready_count=0; sdram_wr_count=0;
sdram_busy_cycles=0; sdram_refresh_count=0;
sdram_req_start_cyc=0; sdram_lat_sum=0; sdram_lat_min=999999; sdram_lat_max=0; sdram_lat_n=0;
sdram_prev_state_is_refwait=1'b0;
end
always @(posedge clk) begin
if (measure_en) begin
active_count = slot_busy_bit[0];
for (ni = 1; ni < N_SLOTS_CFG; ni = ni + 1) active_count = active_count + slot_busy_bit[ni];
active_hist[active_count] <= active_hist[active_count] + 1;
any_tile_bit = slot_tile_bit[0];
for (ni = 1; ni < N_SLOTS_CFG; ni = ni + 1) any_tile_bit = any_tile_bit | slot_tile_bit[ni];
for (ni = 0; ni < N_SLOTS_CFG; ni = ni + 1)
if (slot_tile_bit[ni]) useful_mac_cycles <= useful_mac_cycles + 1;
if (any_tile_bit) begin
if (first_tile_cyc < 0) first_tile_cyc <= total_cycles;
last_tile_cyc <= total_cycles;
end
// ---- real SDRAM controller port (single physical chip,
// all weight-fetch traffic funnels through this one
// instance) ----
if (u_nmp.u_sdram_backend.u_sdram_ctrl.req) begin
sdram_req_count <= sdram_req_count + 1;
sdram_req_start_cyc <= total_cycles;
if (u_nmp.u_sdram_backend.u_sdram_ctrl.wr) sdram_wr_count <= sdram_wr_count + 1;
end
if (u_nmp.u_sdram_backend.u_sdram_ctrl.ready) begin
sdram_ready_count <= sdram_ready_count + 1;
sdram_lat_sum <= sdram_lat_sum + (total_cycles - sdram_req_start_cyc);
sdram_lat_n <= sdram_lat_n + 1;
if ((total_cycles - sdram_req_start_cyc) < sdram_lat_min) sdram_lat_min <= (total_cycles - sdram_req_start_cyc);
if ((total_cycles - sdram_req_start_cyc) > sdram_lat_max) sdram_lat_max <= (total_cycles - sdram_req_start_cyc);
end
if (u_nmp.u_sdram_backend.u_sdram_ctrl.busy) sdram_busy_cycles <= sdram_busy_cycles + 1;
sdram_prev_state_is_refwait <= (u_nmp.u_sdram_backend.u_sdram_ctrl.state == 5'd9);
if (u_nmp.u_sdram_backend.u_sdram_ctrl.state == 5'd9 && !sdram_prev_state_is_refwait)
sdram_refresh_count <= sdram_refresh_count + 1;
end
end
task automatic report_step17_instrumentation;
real active_pct [0:4];
real util_pct, startup_cycles, drain_cycles;
real sdram_avg_lat, sdram_busy_pct, sdram_bytes_per_cycle;
integer kk, total_tiles_all;
begin
total_tiles_all = 0;
for (kk = 0; kk < N_SLOTS_CFG; kk = kk + 1) total_tiles_all = total_tiles_all + slot_tiles_delivered[kk];
$display(" ---- STEP17 Part B: cycle decomposition ----");
for (kk = 0; kk <= N_SLOTS_CFG; kk = kk + 1) begin
active_pct[kk] = (total_cycles > 0) ? (100.0*active_hist[kk]/total_cycles) : 0.0;
$display(" active_slots=%0d: %0d cycles (%0.2f%%)", kk, active_hist[kk], active_pct[kk]);
end
util_pct = (total_cycles > 0) ? (100.0*useful_mac_cycles/(total_cycles*1.0*N_SLOTS_CFG)) : 0.0;
$display(" useful_mac_cycles (slot-tile-delivery events, summed)=%0d (%0.2f%% of total_cycles*N_SLOTS)", useful_mac_cycles, util_pct);
startup_cycles = (first_tile_cyc >= 0) ? (1.0*first_tile_cyc) : 0.0;
drain_cycles = (last_tile_cyc >= 0) ? (1.0*(total_cycles - last_tile_cyc)) : 0.0;
$display(" startup (cycles before first tile delivered anywhere)=%0.0f", startup_cycles);
$display(" drain (cycles after last tile delivered, until job completion)=%0.0f", drain_cycles);
$display(" ---- STEP17 Part C: SDRAM effectiveness ----");
sdram_avg_lat = (sdram_lat_n > 0) ? (1.0*sdram_lat_sum/sdram_lat_n) : 0.0;
sdram_busy_pct = (total_cycles > 0) ? (100.0*sdram_busy_cycles/total_cycles) : 0.0;
sdram_bytes_per_cycle = (total_cycles > 0) ? (8.0*sdram_ready_count/total_cycles) : 0.0;
$display(" sdram_req_count=%0d sdram_ready_count=%0d sdram_wr_count=%0d (real reads vs writes)",
sdram_req_count, sdram_ready_count, sdram_wr_count);
$display(" sdram_busy_cycles=%0d/%0d (%0.2f%%)", sdram_busy_cycles, total_cycles, sdram_busy_pct);
$display(" sdram_refresh_count=%0d (real AUTO REFRESH commands issued)", sdram_refresh_count);
$display(" sdram_request_latency: min=%0d max=%0d avg=%0.2f cycles (req-to-ready, single controller port)",
sdram_lat_min, sdram_lat_max, sdram_avg_lat);
$display(" sdram_avg_bytes_per_cycle (8 bytes/transaction * ready_count / total_cycles)=%0.4f", sdram_bytes_per_cycle);
end
endtask
// ---- STEP11: weight-stall / prefetch-effectiveness instrumentation ----
// slot_could_present_act: this slot's tile_idx is in-bounds and the
// activation operand for it is already resident -- i.e. everything
// EXCEPT the weight is ready. slot_weight_blocking: on top of that,
// the weight specifically is NOT yet ready (tile_idx>=wgt_ready_count)
// and the FSM is genuinely stalled on it (not mid-read-pipeline, not
// already holding a valid operand).
reg [N_SLOTS_CFG-1:0] slot_could_present_act;
reg [N_SLOTS_CFG-1:0] slot_weight_blocking;
reg [N_SLOTS_CFG-1:0] slot_stalled_this_tile; // sticky per current tile_idx
reg [31:0] prev_tile_idx [0:N_SLOTS_CFG-1];
integer weight_stall_cycles [0:N_SLOTS_CFG-1];
integer tiles_prefetched_clean [0:N_SLOTS_CFG-1]; // consumed w/ zero weight-blocking cycles
integer tiles_consumed_total [0:N_SLOTS_CFG-1];
// plain (non-hierarchical) mirrors of each slot's tile_idx, populated
// combinationally inside the genvar-indexed generate block below --
// a generate-block instance array (GEN_SLOT[.]) can only be indexed
// by a constant genvar, not a runtime `for` variable, so the
// sequential accumulation loop reads these plain arrays instead of
// reaching back into the hierarchy with a runtime index.
wire [31:0] slot_tile_idx_w [0:N_SLOTS_CFG-1];
generate
for (gi = 0; gi < N_SLOTS_CFG; gi = gi + 1) begin : GEN_SLOT_PF_MON
assign slot_tile_idx_w[gi] = {16'b0, u_nmp.u_dataflow_core.GEN_SLOT[gi].u_mm.tile_idx};
always @(*) begin
slot_could_present_act[gi] =
({{16{1'b0}}, u_nmp.u_dataflow_core.GEN_SLOT[gi].u_mm.tile_idx} <
{16'b0, u_nmp.u_dataflow_core.GEN_SLOT[gi].u_mm.n_tiles_reg}) &&
({{16{1'b0}}, u_nmp.u_dataflow_core.GEN_SLOT[gi].u_mm.tile_idx} <
{16'b0, u_nmp.u_dataflow_core.GEN_SLOT[gi].u_mm.usable_act});
// nms_memory_manager_stream.v has no read_issued/
// read_ready states (replaced by the rd_ptr/rd_pending
// read-ahead pipeline) -- the equivalent "blocked
// purely on weight readiness, nothing buffered yet"
// condition is simply: consumption pointer in bounds,
// activation ready, weight NOT ready, and no operand
// currently held in the skid buffer awaiting NP.
slot_weight_blocking[gi] =
slot_could_present_act[gi] &&
!(u_nmp.u_dataflow_core.GEN_SLOT[gi].u_mm.tile_idx <
u_nmp.u_dataflow_core.GEN_SLOT[gi].u_mm.wgt_ready_count) &&
!u_nmp.u_dataflow_core.GEN_SLOT[gi].u_mm.operand_valid;
end
end
endgenerate
always @(posedge clk) begin
if (measure_en) begin
for (ni = 0; ni < N_SLOTS_CFG; ni = ni + 1) begin
if (prev_tile_idx[ni] != slot_tile_idx_w[ni]) begin
// moved on to a new tile: clear the sticky flag for it
slot_stalled_this_tile[ni] <= 1'b0;
prev_tile_idx[ni] <= slot_tile_idx_w[ni];
end else if (slot_weight_blocking[ni]) begin
slot_stalled_this_tile[ni] <= 1'b1;
weight_stall_cycles[ni] <= weight_stall_cycles[ni] + 1;
end
if (slot_tile_bit[ni]) begin
tiles_consumed_total[ni] <= tiles_consumed_total[ni] + 1;
if (!slot_stalled_this_tile[ni])
tiles_prefetched_clean[ni] <= tiles_prefetched_clean[ni] + 1;
end
end
end
end
// Director/dependency bookkeeping
integer jobs_allocated, jobs_completed, wakeups;
integer waiting_sum, ready_sum, dispatched_sum, sample_count;
// Occupancy sampling is EXPENSIVE (a full N_NODES=512 scan) and is
// only needed for the small/structural workloads (A/B/E/F), not
// for the large neuron counts (C/D) where it would dominate
// simulation wall-time for no real benefit (per-slot/PSRAM/tile
// counters below are cheap and always collected). Gated by
// sample_occupancy, set per-workload.
reg sample_occupancy;
integer scan_i;
integer waiting_now, ready_now, dispatched_now;
always @(posedge clk) begin
if (measure_en) begin
total_cycles <= total_cycles + 1;
if (u_nmp.u_arbiter.owner != 0) psram_busy_cycles <= psram_busy_cycles + 1;
for (ni = 0; ni < N_SLOTS_CFG; ni = ni + 1) begin
if (slot_busy_bit[ni]) slot_busy_cycles[ni] <= slot_busy_cycles[ni] + 1;
if (slot_tile_bit[ni]) slot_tiles_delivered[ni] <= slot_tiles_delivered[ni] + 1;
end
if (u_nmp.u_dataflow_core.dm_ready_valid && u_nmp.u_dataflow_core.dm_ready_ready)
jobs_allocated <= jobs_allocated + 1;
if (u_nmp.u_dataflow_core.dir_job_out_done)
jobs_completed <= jobs_completed + 1;
if (u_nmp.u_dataflow_core.dm_producer_done_valid)
wakeups <= wakeups + 1;
if (sample_occupancy) begin
waiting_now = 0; ready_now = 0; dispatched_now = 0;
for (scan_i = 0; scan_i < N_NODES; scan_i = scan_i + 1) begin
case (u_nmp.u_dataflow_core.u_dep_mgr.node_state[scan_i])
2'd1: waiting_now = waiting_now + 1;
2'd2: ready_now = ready_now + 1;
2'd3: dispatched_now = dispatched_now + 1;
default: ;
endcase
end
waiting_sum <= waiting_sum + waiting_now;
ready_sum <= ready_sum + ready_now;
dispatched_sum <= dispatched_sum + dispatched_now;
sample_count <= sample_count + 1;
end
end
end
task automatic reset_instrumentation(input do_sample_occupancy);
integer k;
begin
active_hist[0]=0; active_hist[1]=0; active_hist[2]=0; active_hist[3]=0; active_hist[4]=0;
useful_mac_cycles = 0; first_tile_cyc = -1; last_tile_cyc = -1;
sdram_req_count=0; sdram_ready_count=0; sdram_wr_count=0;
sdram_busy_cycles=0; sdram_refresh_count=0;
sdram_req_start_cyc=0; sdram_lat_sum=0; sdram_lat_min=999999; sdram_lat_max=0; sdram_lat_n=0;
total_cycles = 0; psram_busy_cycles = 0;
jobs_allocated = 0; jobs_completed = 0; wakeups = 0;
waiting_sum = 0; ready_sum = 0; dispatched_sum = 0; sample_count = 0;
sample_occupancy = do_sample_occupancy;
for (k = 0; k < N_SLOTS_CFG; k = k + 1) begin
slot_busy_cycles[k] = 0;
slot_tiles_delivered[k] = 0;
weight_stall_cycles[k] = 0;
tiles_prefetched_clean[k] = 0;
tiles_consumed_total[k] = 0;
slot_stalled_this_tile[k] = 1'b0;
prev_tile_idx[k] = 32'hFFFFFFFF;
end
end
endtask
task automatic report_instrumentation(input [255:0] label, input integer n_neurons_completed);
integer k, total_tiles;
integer total_weight_stall_cycles, total_tiles_consumed_all, total_tiles_prefetched_clean;
real avg_waiting, avg_ready, avg_dispatched;
real psram_util, sustained_mac_per_cycle, wallclock_us;
real processor_utilization, weight_stall_pct, prefetch_effectiveness_pct;
begin
total_tiles = 0;
for (k = 0; k < N_SLOTS_CFG; k = k + 1) total_tiles = total_tiles + slot_tiles_delivered[k];
avg_waiting = (sample_count > 0) ? (1.0*waiting_sum/sample_count) : 0.0;
avg_ready = (sample_count > 0) ? (1.0*ready_sum/sample_count) : 0.0;
avg_dispatched = (sample_count > 0) ? (1.0*dispatched_sum/sample_count) : 0.0;
psram_util = (total_cycles > 0) ? (100.0*psram_busy_cycles/total_cycles) : 0.0;
sustained_mac_per_cycle = (total_cycles > 0) ? (1.0*total_tiles*P_IN/total_cycles) : 0.0;
wallclock_us = total_cycles * CLK_PERIOD / 1000.0;
$display("---- BENCHMARK REPORT: %0s ----", label);
$display(" total_cycles=%0d wallclock_us=%0.3f", total_cycles, wallclock_us);
$display(" neurons_completed=%0d tiles_delivered(real)=%0d", n_neurons_completed, total_tiles);
$display(" jobs_allocated=%0d jobs_completed=%0d dependency_wakeups=%0d", jobs_allocated, jobs_completed, wakeups);
$display(" shared AR (activation+result) arbiter-side utilization: %0.1f%% (%0d/%0d busy cycles)", psram_util, psram_busy_cycles, total_cycles);
for (k = 0; k < N_SLOTS_CFG; k = k + 1)
$display(" slot %0d: busy=%0d/%0d (%0.1f%%) tiles=%0d", k, slot_busy_cycles[k], total_cycles,
(total_cycles>0)?(100.0*slot_busy_cycles[k]/total_cycles):0.0, slot_tiles_delivered[k]);
if (sample_count > 0)
$display(" dependency_manager avg occupancy (sampled every measured cycle): waiting=%0.2f ready=%0.2f dispatched=%0.2f", avg_waiting, avg_ready, avg_dispatched);
else
$display(" dependency_manager occupancy: NOT SAMPLED for this workload (N_NODES scan skipped for large neuron counts to keep simulation time reasonable)");
$display(" DERIVED: sustained end-to-end MAC/cycle = %0.4f (real tiles*%0d / real total_cycles)", sustained_mac_per_cycle, P_IN);
if (n_neurons_completed > 0)
$display(" DERIVED: cycles/neuron = %0.2f", 1.0*total_cycles/n_neurons_completed);
if (total_tiles > 0)
$display(" DERIVED: cycles/tile = %0.2f", 1.0*total_cycles/total_tiles);
// ---- STEP11 metrics ----
total_weight_stall_cycles = 0; total_tiles_consumed_all = 0; total_tiles_prefetched_clean = 0;
for (k = 0; k < N_SLOTS_CFG; k = k + 1) begin
total_weight_stall_cycles = total_weight_stall_cycles + weight_stall_cycles[k];
total_tiles_consumed_all = total_tiles_consumed_all + tiles_consumed_total[k];
total_tiles_prefetched_clean = total_tiles_prefetched_clean + tiles_prefetched_clean[k];
end
processor_utilization = (total_cycles > 0) ? (100.0*total_tiles/(total_cycles*1.0)) : 0.0;
weight_stall_pct = (total_cycles > 0) ? (100.0*total_weight_stall_cycles/(total_cycles*N_SLOTS_CFG*1.0)) : 0.0;
prefetch_effectiveness_pct = (total_tiles_consumed_all > 0) ?
(100.0*total_tiles_prefetched_clean/(total_tiles_consumed_all*1.0)) : 0.0;
$display(" [STEP11] PFD=%0d weight_stall_cycles(sum,all slots)=%0d (%0.2f%% of total_cycles*N_SLOTS)",
PFD_CFG, total_weight_stall_cycles, weight_stall_pct);
$display(" [STEP11] tiles_consumed=%0d tiles_prefetched_clean(zero weight-block before consumption)=%0d",
total_tiles_consumed_all, total_tiles_prefetched_clean);
$display(" [STEP11] DERIVED: prefetch_effectiveness = %0.2f%%", prefetch_effectiveness_pct);
$display(" [STEP11] DERIVED: processor_utilization (tiles*P_IN-equivalent proxy, see sustained MAC/cycle) reference sustained_mac_per_cycle=%0.4f", sustained_mac_per_cycle);
end
endtask
// ============================================================
// Workload generators
// ============================================================
integer errors, tests;
integer wd;
// A/B/C/D: shared-input dense layer. Generates the shared X
// vector, then N independent (neuron, weight-vector) jobs, each
// verified bit-exact against the golden model.
task automatic run_dense_layer(
input [255:0] label,
input integer n_neurons,
input integer n_tiles_count,
input [NODE_IDW-1:0] node_base,
input [ADDR_WIDTH-1:0] x_base,
input [ADDR_WIDTH-1:0] w_base,
input [ADDR_WIDTH-1:0] res_base,
input sample_occ
);
integer n, t, k, len, acc;
reg signed [7:0] xv, wv, golden, real_y;
reg [MAX_DEPS*NODE_IDW-1:0] no_deps;
integer completed, wd2;
begin
len = n_tiles_count * P_IN;
no_deps = {(MAX_DEPS*NODE_IDW){1'b0}};
// shared input vector
for (k = 0; k < len; k = k + 1)
poke_byte(x_base + k, ((k % 8) + 1));
reset_instrumentation(sample_occ);
measure_en = 1'b1;
for (n = 0; n < n_neurons; n = n + 1) begin
acc = 0;
for (t = 0; t < n_tiles_count; t = t + 1) begin
for (k = 0; k < P_IN; k = k + 1) begin
xv = peek_byte(x_base + t*P_IN + k);
wv = (((n + t*P_IN + k) % 8) + 1);
poke_byte_weight(w_base + n*len + t*P_IN + k, wv);
acc = acc + xv*wv;
end
end
golden = relu_sat(acc);
poke_byte(res_base + n, 8'sd0); // poison, must NOT still be 0 after completion (unless golden IS 0 -- checked separately)
register_node(node_base + n[NODE_IDW-1:0], 0, no_deps,
x_base, w_base + n*len, n_tiles_count[15:0], res_base + n);
if ((n % 32) == 0) begin
$display(" [%0s] registered %0d/%0d", label, n+1, n_neurons);
$fflush;
end
end
$display(" [%0s] all %0d neurons registered, waiting for completion...", label, n_neurons);
$fflush;
// wait for all n_neurons completions
completed = 0; wd2 = 0;
while (completed < n_neurons && wd2 < 2000000) begin
@(posedge clk);
wd2 = wd2 + 1;
completed = jobs_completed;
if ((wd2 % 20000) == 0) begin
$display(" [%0s] watchdog %0d: completed=%0d/%0d total_cycles=%0d", label, wd2, completed, n_neurons, total_cycles);
`ifdef STEP16_DEBUG_TRACE
$display(" slot0: mm.state=%0d tile_idx=%0d n_tiles_reg=%0d wgt_ready_count=%0d usable_act=%0d op_valid=%0d op_ready=%0d",
u_nmp.u_dataflow_core.GEN_SLOT[0].u_mm.state,
u_nmp.u_dataflow_core.GEN_SLOT[0].u_mm.tile_idx,
u_nmp.u_dataflow_core.GEN_SLOT[0].u_mm.n_tiles_reg,
u_nmp.u_dataflow_core.GEN_SLOT[0].u_mm.wgt_ready_count,
u_nmp.u_dataflow_core.GEN_SLOT[0].u_mm.usable_act,
u_nmp.u_dataflow_core.GEN_SLOT[0].mm_operand_valid,
u_nmp.u_dataflow_core.GEN_SLOT[0].mm_operand_ready);
$display(" slot1: mm.state=%0d tile_idx=%0d n_tiles_reg=%0d wgt_ready_count=%0d usable_act=%0d op_valid=%0d op_ready=%0d",
u_nmp.u_dataflow_core.GEN_SLOT[1].u_mm.state,
u_nmp.u_dataflow_core.GEN_SLOT[1].u_mm.tile_idx,
u_nmp.u_dataflow_core.GEN_SLOT[1].u_mm.n_tiles_reg,
u_nmp.u_dataflow_core.GEN_SLOT[1].u_mm.wgt_ready_count,
u_nmp.u_dataflow_core.GEN_SLOT[1].u_mm.usable_act,
u_nmp.u_dataflow_core.GEN_SLOT[1].mm_operand_valid,
u_nmp.u_dataflow_core.GEN_SLOT[1].mm_operand_ready);
$display(" sdram: req=%0d busy=%0d ready=%0d req_pending=%0d state=%0d | arb: owner=%0d pending=%0b wide_req=%0b wide_ready=%0b",
u_nmp.u_sdram_backend.u_sdram_ctrl.req,
u_nmp.u_sdram_backend.u_sdram_ctrl.busy,
u_nmp.u_sdram_backend.u_sdram_ctrl.ready,
u_nmp.u_sdram_backend.u_sdram_ctrl.req_pending,
u_nmp.u_sdram_backend.u_sdram_ctrl.state,
u_nmp.u_arbiter_wide.owner,
u_nmp.u_arbiter_wide.pending,
u_nmp.wide_slot_mem_req,
u_nmp.wide_slot_mem_ready);
`endif
$fflush;
end
end
repeat(5) @(posedge clk);
measure_en = 1'b0;
tests = tests + 1;
if (completed < n_neurons) begin
$display("FAIL %0s: only %0d/%0d neurons completed within watchdog", label, completed, n_neurons);
errors = errors + 1;
end else begin : check_block
integer local_errors;
local_errors = 0;
for (n = 0; n < n_neurons; n = n + 1) begin
acc = 0;
for (t = 0; t < n_tiles_count; t = t + 1)
for (k = 0; k < P_IN; k = k + 1)
acc = acc + peek_byte(x_base + t*P_IN + k) * peek_byte_weight(w_base + n*len + t*P_IN + k);
golden = relu_sat(acc);
real_y = peek_byte(res_base + n);
if (real_y !== golden) begin
$display("FAIL %0s neuron %0d: real=%0d golden=%0d", label, n, real_y, golden);
local_errors = local_errors + 1;
end
end
if (local_errors == 0)
$display("PASS %0s: all %0d neurons bit-exact vs golden", label, n_neurons);
else
errors = errors + 1;
end
report_instrumentation(label, n_neurons);
report_step17_instrumentation;
end
endtask
// E: Multilayer (8 layer-1 random neurons -> shared hidden vector
// -> 2 layer-2 neurons consuming it, real dependency wake-up +
// real cross-node data forwarding through real PSRAM).
localparam L1_N = 8;
localparam L2_N = 2;
integer rand_seed;
task automatic run_multilayer(
input [NODE_IDW-1:0] node_base,
input [ADDR_WIDTH-1:0] l1x_base, input [ADDR_WIDTH-1:0] l1w_base,
input [ADDR_WIDTH-1:0] hidden_base,
input [ADDR_WIDTH-1:0] l2w_base, input [ADDR_WIDTH-1:0] l2res_base
);
integer n, k, acc, completed, wd2;
reg signed [7:0] xv, wv, golden_l1 [0:L1_N-1], golden_l2, real_y;
reg [MAX_DEPS*NODE_IDW-1:0] no_deps, l2_deps;
integer local_errors;
begin
no_deps = {(MAX_DEPS*NODE_IDW){1'b0}};
l2_deps = {(MAX_DEPS*NODE_IDW){1'b0}};
for (n = 0; n < L1_N; n = n + 1)
l2_deps[n*NODE_IDW +: NODE_IDW] = node_base + n[NODE_IDW-1:0];
rand_seed = 32'hC0FFEE01;
$display("RANDOM SEED (workload E, layer-1 data) = 32'h%08h", rand_seed);
reset_instrumentation(1'b1);
measure_en = 1'b1;
for (n = 0; n < L1_N; n = n + 1) begin
acc = 0;
for (k = 0; k < P_IN; k = k + 1) begin
xv = $random(rand_seed) % 9; // deterministic PRNG stream, range roughly [-8,8]
wv = $random(rand_seed) % 9;
poke_byte(l1x_base + n*P_IN + k, xv);
poke_byte(l1w_base + n*P_IN + k, wv);
acc = acc + xv*wv;
end
golden_l1[n] = relu_sat(acc);
poke_byte(hidden_base + n, 8'sd0); // poison hidden slot
register_node(node_base + n[NODE_IDW-1:0], 0, no_deps,
l1x_base + n*P_IN, l1w_base + n*P_IN, 16'd1, hidden_base + n);
end
for (n = 0; n < L2_N; n = n + 1) begin
for (k = 0; k < P_IN; k = k + 1)
poke_byte(l2w_base + n*P_IN + k, ((n + k) % 6) + 1);
register_node(node_base + L1_N[NODE_IDW-1:0] + n[NODE_IDW-1:0], L1_N[$clog2(MAX_DEPS+1)-1:0], l2_deps,
hidden_base, l2w_base + n*P_IN, 16'd1, l2res_base + n);
end
completed = 0; wd2 = 0;
while (completed < (L1_N+L2_N) && wd2 < 2000000) begin
@(posedge clk); wd2 = wd2 + 1; completed = jobs_completed;
end
repeat(5) @(posedge clk);
measure_en = 1'b0;
tests = tests + 1;
local_errors = 0;
if (completed < (L1_N+L2_N)) begin
$display("FAIL Multilayer: only %0d/%0d nodes completed", completed, L1_N+L2_N);
local_errors = local_errors + 1;
end else begin
for (n = 0; n < L1_N; n = n + 1) begin
real_y = peek_byte(hidden_base + n);
if (real_y !== golden_l1[n]) begin
$display("FAIL Multilayer L1 neuron %0d: real=%0d golden=%0d", n, real_y, golden_l1[n]);
local_errors = local_errors + 1;
end
end
for (n = 0; n < L2_N; n = n + 1) begin
acc = 0;
for (k = 0; k < P_IN; k = k + 1)
acc = acc + golden_l1[k] * peek_byte(l2w_base + n*P_IN + k);
golden_l2 = relu_sat(acc);
real_y = peek_byte(l2res_base + n);
if (real_y !== golden_l2) begin
$display("FAIL Multilayer L2 neuron %0d: real=%0d golden=%0d (using REAL L1 hidden values)", n, real_y, golden_l2);
local_errors = local_errors + 1;
end
end
end
if (local_errors == 0) $display("PASS Multilayer: 8 L1 (random) -> 2 L2 neurons, all bit-exact, real cross-node forwarding via real PSRAM");
else errors = errors + 1;
report_instrumentation("E-Multilayer", L1_N+L2_N);
end
endtask
// F: DAG diamond+fan-in (A,B indep; C dep-A; D dep-B; E dep-C&D
// [2-hop]; F dep-A,B,C [mixed, 3 producers])
task automatic run_dag(
input [NODE_IDW-1:0] node_base,
input [ADDR_WIDTH-1:0] x_base, input [ADDR_WIDTH-1:0] w_base, input [ADDR_WIDTH-1:0] res_base
);
integer n, k, acc, completed, wd2, local_errors;
reg signed [7:0] golden [0:5];
reg signed [7:0] real_y;
reg [MAX_DEPS*NODE_IDW-1:0] deps;
reg [NODE_IDW-1:0] idA, idB, idC, idD, idE, idF;
begin
idA = node_base+0; idB = node_base+1; idC = node_base+2;
idD = node_base+3; idE = node_base+4; idF = node_base+5;
// Each of the 6 nodes: its own small independent 8-input
// job (deterministic, distinct per node) -- dependencies
// here are purely about SCHEDULING/wake-up order, not
// data forwarding (workload E already covers that).
for (n = 0; n < 6; n = n + 1) begin
acc = 0;
for (k = 0; k < P_IN; k = k + 1) begin
poke_byte(x_base + n*P_IN + k, ((n+k)%4)+1);
poke_byte(w_base + n*P_IN + k, ((n+k)%5)+1);
acc = acc + peek_byte(x_base+n*P_IN+k)*peek_byte(w_base+n*P_IN+k);
end
golden[n] = relu_sat(acc);
poke_byte(res_base + n, 8'sd0);
end
reset_instrumentation(1'b1);
measure_en = 1'b1;
deps = {(MAX_DEPS*NODE_IDW){1'b0}};
register_node(idA, 0, deps, x_base+0*P_IN, w_base+0*P_IN, 16'd1, res_base+0);
register_node(idB, 0, deps, x_base+1*P_IN, w_base+1*P_IN, 16'd1, res_base+1);
deps = {(MAX_DEPS*NODE_IDW){1'b0}}; deps[0*NODE_IDW+:NODE_IDW] = idA;
register_node(idC, 1, deps, x_base+2*P_IN, w_base+2*P_IN, 16'd1, res_base+2);
deps = {(MAX_DEPS*NODE_IDW){1'b0}}; deps[0*NODE_IDW+:NODE_IDW] = idB;
register_node(idD, 1, deps, x_base+3*P_IN, w_base+3*P_IN, 16'd1, res_base+3);
deps = {(MAX_DEPS*NODE_IDW){1'b0}}; deps[0*NODE_IDW+:NODE_IDW] = idC; deps[1*NODE_IDW+:NODE_IDW] = idD;
register_node(idE, 2, deps, x_base+4*P_IN, w_base+4*P_IN, 16'd1, res_base+4);
deps = {(MAX_DEPS*NODE_IDW){1'b0}}; deps[0*NODE_IDW+:NODE_IDW] = idA; deps[1*NODE_IDW+:NODE_IDW] = idB; deps[2*NODE_IDW+:NODE_IDW] = idC;
register_node(idF, 3, deps, x_base+5*P_IN, w_base+5*P_IN, 16'd1, res_base+5);
completed = 0; wd2 = 0;
while (completed < 6 && wd2 < 2000000) begin @(posedge clk); wd2=wd2+1; completed = jobs_completed; end
repeat(5) @(posedge clk);
measure_en = 1'b0;
tests = tests + 1;
local_errors = 0;
if (completed < 6) begin
$display("FAIL DAG: only %0d/6 nodes completed", completed);
local_errors = local_errors + 1;
end else begin
for (n = 0; n < 6; n = n + 1) begin
real_y = peek_byte(res_base+n);
if (real_y !== golden[n]) begin
$display("FAIL DAG node %0d: real=%0d golden=%0d", n, real_y, golden[n]);
local_errors = local_errors + 1;
end
end
end
if (local_errors == 0) $display("PASS DAG: 6-node diamond+fan-in (2-hop transitive wake-up, 3-producer mixed-depth dependency), all bit-exact");
else errors = errors + 1;
report_instrumentation("F-DAG", 6);
end
endtask
initial begin
errors = 0; tests = 0;
rst = 1; reg_valid = 0; reg_node_id = 0; reg_required = 0; reg_producer_ids = 0;
reg_x_base = 0; reg_w_base = 0; reg_n_tiles = 0; reg_result_addr = 0;
measure_en = 0;
repeat(5) @(posedge clk);
rst = 0;
$display("========================================");
$display("NMS D-Stress benchmark (STEP19, SINGLE SDRAM (AS4C4M16SA-6TIN) for weights+activations+results, no PSRAM anywhere) -- N_SLOTS_CFG=%0d PFD_CFG=%0d", N_SLOTS_CFG, PFD_CFG);
$display("========================================");
wait (u_nmp.u_sdram_backend.u_sdram_ctrl.state == u_nmp.u_sdram_backend.u_sdram_ctrl.S_IDLE);
@(posedge clk);
// Official V2 memory map (datasheet ch.5): weights @ 0x010000,
// activations @ 0x200000, results @ 0x300000 -- non-overlapping
// 1MB-aligned regions in the single SDRAM.
run_dense_layer("D-Stress", 256, 16, 16'd400, 26'h200000, 26'h010000, 26'h300000, 1'b0);
// FPGA_DATA_READY check: the whole graph (256 nodes) just
// finished and no new work has been registered -- data_ready
// must be asserted (system-idle sticky flag, see
// nms_dataflow_core_sdram.v). A few idle cycles for the
// busy->idle edge to settle before sampling.
repeat (4) @(posedge clk);
if (u_nmp.data_ready !== 1'b1) begin
$display("FAIL data_ready: expected 1 after graph completion, got %b", u_nmp.data_ready);
errors = errors + 1;
end else begin
$display("PASS data_ready: correctly asserted after graph completion");
end
$display("========================================");
if (errors == 0)
$display("ALL %0d WORKLOAD SUITES PASSED (N_SLOTS_CFG=%0d, PFD_CFG=%0d, SINGLE SDRAM for weights+activations+results, no PSRAM)", tests, N_SLOTS_CFG, PFD_CFG);
else
$display("FAILED: %0d/%0d workload suite(s) had errors -- see messages above", errors, tests);
$display("========================================");
$finish;
end
endmodule
+253
View File
@@ -0,0 +1,253 @@
`timescale 1ns/1ps
// ================================================================
// EXP-0056 -- fork of dependency_manager.v: the ONLY change is
// first_ready_idx/any_ready, replaced with priority_encoder_lsb.v
// (see that computation's own inline comment for the full rationale
// and the fix-pattern precedent -- ERR-0027/ERR-0028/ERR-0029). This
// targets the real critical path found blocking N_SLOTS=16 timing
// closure on the LFE5U-85F (measured: worst 23.52MHz vs 64MHz target,
// see hardware/v2/logs/experiments.log EXP-0056). Everything else in
// this module is byte-for-byte identical to dependency_manager.v.
//
// Everything below this point is the ORIGINAL module's own header,
// preserved as-is:
//
// FPGA-Neural V2 -- Dependency Manager (M6, docs/v2-description.md §10)
//
// Holds a small table of N_NODES job descriptors, each tracking:
// node_id, state (EMPTY/WAITING/READY/DISPATCHED),
// required_dependencies, resolved_dependencies, producer_ids[MAX_DEPS]
// (§10's exact field list), plus the job descriptor fields
// (x_base/w_base/n_tiles/result_addr) needed to hand the node off to
// the Neural Director (M5) once it becomes READY.
//
// A node with required_dependencies==0 is immediately READY on
// registration (no producers to wait for -- a graph's own input
// nodes, or a fully-independent job). When a PRODUCER completes
// (producer_done_valid/producer_done_node_id, tagged by whichever
// node just finished -- fed from the Director's own job_out_done/
// job_out_slot, resolved back to a node_id by the caller), every
// OTHER node that lists that producer among its own producer_ids
// gets its resolved_dependencies incremented -- a single producer
// can satisfy MULTIPLE waiting consumers this way (§10 "risultati
// condivisi... più consumer"), and a node depending on several
// producers accumulates resolved_dependencies across separate
// producer-done events ("dipendenze multiple").
//
// Ready nodes are handed to the Director one at a time via a
// valid/ready producer interface (ready_valid/ready_ready), backpressure-
// safe (§10 "backpressure"): a node stays READY, occupying its table
// slot, until the consumer (Director) actually accepts it.
//
// Scope note (see hardware/v2/logs/decisions.log DEC-0008): §11's
// direct producer-to-consumer VALUE forwarding (bypassing the Result
// Buffer / external memory round-trip) is NOT implemented here --
// this module tracks dependency COUNTS/readiness only ("has this
// node's data become available", not the data itself), which is what
// actually gates scheduling; the job descriptor's result_addr already
// points at wherever the Memory Manager (M4) wrote the producer's
// result, which is how a ready consumer finds its inputs today. Real
// zero-copy forwarding is a possible future optimization (§11 itself:
// "quando possibile"), deferred until measured to matter (§22).
// ================================================================
module dependency_manager_fast #(
parameter N_NODES = 16,
parameter MAX_DEPS = 4,
parameter ADDR_WIDTH = 26
)(
input wire clk,
input wire rst,
// ---- node registration (host / graph loader) ----
input wire reg_valid,
output wire reg_ready,
input wire [$clog2(N_NODES)-1:0] reg_node_id,
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
input wire [ADDR_WIDTH-1:0] reg_x_base,
input wire [ADDR_WIDTH-1:0] reg_w_base,
input wire [15:0] reg_n_tiles,
input wire [ADDR_WIDTH-1:0] reg_result_addr,
// ---- producer completion notification ----
input wire producer_done_valid,
input wire [$clog2(N_NODES)-1:0] producer_done_node_id,
// ---- ready job output (to neural_director.v's job_in_* port) ----
output reg ready_valid,
input wire ready_ready,
output reg [$clog2(N_NODES)-1:0] ready_node_id,
output reg [ADDR_WIDTH-1:0] ready_x_base,
output reg [ADDR_WIDTH-1:0] ready_w_base,
output reg [15:0] ready_n_tiles,
output reg [ADDR_WIDTH-1:0] ready_result_addr,
// FPGA_DATA_READY support: high while at least one registered node
// has not yet been handed to the Director (ST_WAITING or ST_READY --
// ST_DISPATCHED is deliberately excluded, since dispatched work is
// tracked downstream by neural_director.v's own queue/slot state,
// not here -- see this file's own ST_DISPATCHED comment).
output wire any_pending
);
localparam ST_EMPTY = 2'd0;
localparam ST_WAITING = 2'd1;
localparam ST_READY = 2'd2;
localparam ST_DISPATCHED = 2'd3;
localparam NODE_IDW = $clog2(N_NODES);
localparam REQW = $clog2(MAX_DEPS+1);
reg [1:0] node_state [0:N_NODES-1];
reg [REQW-1:0] node_required [0:N_NODES-1];
reg [REQW-1:0] node_resolved [0:N_NODES-1];
reg [NODE_IDW-1:0] node_producer_ids [0:N_NODES-1][0:MAX_DEPS-1];
reg [ADDR_WIDTH-1:0] node_x_base [0:N_NODES-1];
reg [ADDR_WIDTH-1:0] node_w_base [0:N_NODES-1];
reg [15:0] node_n_tiles [0:N_NODES-1];
reg [ADDR_WIDTH-1:0] node_result_addr [0:N_NODES-1];
// A node id doubles as its own table slot index (§10's example
// literally addresses nodes by id: "node 37") -- N_NODES must
// therefore cover the full id range a caller intends to use.
assign reg_ready = (node_state[reg_node_id] == ST_EMPTY);
// ---- EXP-0056: priority-encoded first READY node, via a
// recursive binary-tree lowest-set-bit encoder (O(log2(N_NODES))
// depth) instead of the original serial for-loop scan (O(N_NODES)
// depth, the SAME architectural anti-pattern already fixed twice
// elsewhere in this project -- ERR-0027/ERR-0028/ERR-0029 -- see
// priority_encoder_lsb.v's own header for the full rationale).
// Semantically identical to the original: ready_oh's lowest set
// bit is the lowest node index currently READY, matching the
// original loop's own "last (lowest-index) match wins" behavior
// exactly -- verified bit-exact against the original module by
// tb_dependency_manager_fast.v before this fork was integrated
// anywhere.
// ============================================================
wire [N_NODES-1:0] ready_oh;
generate
genvar gi;
for (gi = 0; gi < N_NODES; gi = gi + 1) begin : GEN_READY_OH
assign ready_oh[gi] = (node_state[gi] == ST_READY);
end
endgenerate
wire [NODE_IDW-1:0] first_ready_idx;
wire any_ready;
priority_encoder_lsb #(.WIDTH(N_NODES)) u_ready_penc (
.in(ready_oh), .idx(first_ready_idx), .valid(any_ready)
);
// ---- FPGA_DATA_READY support (see any_pending port comment above).
// Originally a combinational OR-reduce over node_state[0:N_NODES-1]
// (16-wide), which added real fan-out load onto node_state -- a
// signal this session's own real P&R critical-path traces later
// showed sitting on the SAME already-congested job_out_slot ->
// node_resolved/node_state broadcast path (routing-dominated,
// 76-84%). Replaced with a synchronous up/down counter: +1 on a
// node's own registration acceptance (reg_valid&&reg_ready --
// exactly when it enters WAITING/READY), -1 on its own dispatch
// acceptance (ready_valid&&ready_ready -- exactly when it leaves
// WAITING/READY for DISPATCHED). registered-minus-dispatched is
// mathematically identical to the original OR-reduce's own
// "any node currently WAITING or READY" condition (DEC-0008: nodes
// are never reclaimed mid-run, so every node visits EMPTY ->
// {WAITING or READY} -> DISPATCHED exactly once), but reads a
// single small registered counter instead of scanning a wide array
// every cycle -- zero added fan-out on the congested signals. ----
localparam PENDW = $clog2(N_NODES+1);
reg [PENDW-1:0] pending_count;
assign any_pending = (pending_count != {PENDW{1'b0}});
integer ni, di;
always @(posedge clk) begin
if (rst) begin
for (ni = 0; ni < N_NODES; ni = ni + 1) begin
node_state[ni] <= ST_EMPTY;
node_required[ni] <= {REQW{1'b0}};
node_resolved[ni] <= {REQW{1'b0}};
end
ready_valid <= 1'b0;
pending_count <= {PENDW{1'b0}};
end else begin
// pending_count: +1 on registration acceptance, -1 on
// dispatch acceptance; a same-cycle occurrence of both is a
// net zero change (no assignment needed, old value holds).
case ({(reg_valid && reg_ready), (ready_valid && ready_ready)})
2'b10: pending_count <= pending_count + 1'b1;
2'b01: pending_count <= pending_count - 1'b1;
default: ; // 00 or 11: no net change
endcase
// ---- registration: create a new WAITING (or immediately
// READY, if required==0) node entry. ----
if (reg_valid && reg_ready) begin
node_required[reg_node_id] <= reg_required;
node_resolved[reg_node_id] <= {REQW{1'b0}};
node_x_base[reg_node_id] <= reg_x_base;
node_w_base[reg_node_id] <= reg_w_base;
node_n_tiles[reg_node_id] <= reg_n_tiles;
node_result_addr[reg_node_id] <= reg_result_addr;
for (di = 0; di < MAX_DEPS; di = di + 1)
node_producer_ids[reg_node_id][di] <= reg_producer_ids[di*NODE_IDW +: NODE_IDW];
node_state[reg_node_id] <= (reg_required == {REQW{1'b0}}) ? ST_READY : ST_WAITING;
end
// ---- wake-up: a completed producer increments
// resolved_dependencies for EVERY WAITING node that lists
// it, independent of the registration above (a node can
// be registered and immediately woken by an in-flight
// producer-done event the same cycle, since both read the
// PRE-edge node_state/node_producer_ids consistently). ----
if (producer_done_valid) begin
for (ni = 0; ni < N_NODES; ni = ni + 1) begin
if (node_state[ni] == ST_WAITING) begin
for (di = 0; di < MAX_DEPS; di = di + 1) begin
if (di < node_required[ni] &&
node_producer_ids[ni][di] == producer_done_node_id) begin
if (node_resolved[ni] + 1'b1 >= node_required[ni])
node_state[ni] <= ST_READY;
node_resolved[ni] <= node_resolved[ni] + 1'b1;
end
end
end
end
end
// ---- dispatch: hand the first READY node to the
// Director, one at a time, backpressure-safe. ----
if (ready_valid && ready_ready) begin
node_state[ready_node_id] <= ST_DISPATCHED;
// ST_DISPATCHED is terminal here (M6 does not yet
// reclaim slots for re-use -- see decisions.log
// DEC-0008): a full graph run allocates N_NODES once.
ready_valid <= 1'b0;
end else if (!ready_valid && any_ready) begin
// Deliberately NOT combined with the dispatch branch
// above into "!ready_valid || (ready_valid&&ready_ready)"
// -- the scan for first_ready_idx is combinational
// over node_state's PRE-edge value, which still shows
// the about-to-be-dispatched node as READY this same
// edge; reloading in the same cycle as a dispatch
// could re-present the SAME node that is simultaneously
// transitioning to DISPATCHED. Reloading strictly the
// cycle AFTER (once ready_valid has genuinely gone
// low and node_state has committed) costs one extra
// idle cycle between consecutive dispatches but is
// unambiguously correct.
ready_valid <= 1'b1;
ready_node_id <= first_ready_idx;
ready_x_base <= node_x_base[first_ready_idx];
ready_w_base <= node_w_base[first_ready_idx];
ready_n_tiles <= node_n_tiles[first_ready_idx];
ready_result_addr <= node_result_addr[first_ready_idx];
end
end
end
endmodule
+93
View File
@@ -0,0 +1,93 @@
`timescale 1ns/1ps
// ============================================================
// EXP-0057 -- double-buffered, per-layer resident weight scratchpad.
//
// One layer's worth of weights (up to LAYER_DEPTH entries of
// DATA_WIDTH bits, real BRAM-style array, same coding idiom as
// nms_weight_packed.v's own per-slot memories) stays resident and is
// read MANY times (once per output position that reuses it -- e.g.
// every spatial position a convolutional filter slides across),
// while the NEXT layer's weights are being fetched into the OTHER
// buffer in the background. Buffers swap only when BOTH conditions
// hold: the compute side has finished consuming the active buffer
// (consume_done) AND the fill side has finished loading the other one
// (fill_done) -- matches this project's own established discipline
// (never swap/overwrite data still in use, same spirit as
// sdram_unified_backend.v's own req_pending latch correctness fixes).
//
// This is deliberately NOT the same thing as the existing per-slot
// nms_weight_packed.v buffer: that one holds MAX_TILES tiles for ONE
// job with no reuse across neurons (D-Stress's own zero-reuse case).
// This module exists for the OPPOSITE traffic pattern -- one weight
// block read many times before being replaced -- which is what a
// convolutional filter (or any weight-stationary dataflow) needs.
// ============================================================
module layer_weight_buffer #(
parameter DATA_WIDTH = 8,
parameter LAYER_DEPTH = 128,
parameter ADDRW = (LAYER_DEPTH <= 1) ? 1 : $clog2(LAYER_DEPTH)
)(
input wire clk,
input wire rst,
// ---- fill side: writes into the INACTIVE buffer ----
input wire fill_we,
input wire [ADDRW-1:0] fill_addr,
input wire [DATA_WIDTH-1:0] fill_data,
input wire fill_done, // pulse: inactive buffer fully loaded
// ---- compute side: reads from the ACTIVE buffer, any number of
// times, any order (real conv access pattern is not necessarily
// sequential -- e.g. im2col-style window reuse) ----
input wire [ADDRW-1:0] rd_addr,
output wire [DATA_WIDTH-1:0] rd_data,
input wire consume_done, // pulse: compute side is done with the active buffer
// ---- swap: happens the cycle AFTER both fill_done and
// consume_done have been seen since the last swap -- order-
// independent (a pulse arriving before the other is latched, not
// dropped), matching this project's own req_pending latch idiom ----
output reg active_sel, // which physical buffer (0/1) is active for reads
output reg swapped // pulses the cycle a swap occurs
);
reg [DATA_WIDTH-1:0] mem0 [0:LAYER_DEPTH-1];
reg [DATA_WIDTH-1:0] mem1 [0:LAYER_DEPTH-1];
reg fill_done_latched, consume_done_latched;
wire do_swap = fill_done_latched && consume_done_latched;
always @(posedge clk) begin
if (fill_we) begin
if (active_sel == 1'b0) mem1[fill_addr] <= fill_data; // fill the INACTIVE one
else mem0[fill_addr] <= fill_data;
end
end
// read from the ACTIVE buffer, combinational (matches
// nms_weight_packed.v's own same-cycle-bypass-free combinational
// read convention for a single-port style array read)
assign rd_data = active_sel ? mem1[rd_addr] : mem0[rd_addr];
always @(posedge clk) begin
if (rst) begin
active_sel <= 1'b0;
swapped <= 1'b0;
fill_done_latched <= 1'b0;
consume_done_latched <= 1'b0;
end else begin
swapped <= 1'b0;
if (fill_done) fill_done_latched <= 1'b1;
if (consume_done) consume_done_latched <= 1'b1;
if (do_swap) begin
active_sel <= ~active_sel;
swapped <= 1'b1;
fill_done_latched <= 1'b0;
consume_done_latched <= 1'b0;
end
end
end
endmodule
+70
View File
@@ -0,0 +1,70 @@
`timescale 1ns/1ps
// ============================================================
// EXP-0056 -- generic, recursive binary-tree priority encoder
// (lowest-set-bit wins), O(log2(WIDTH)) depth.
//
// MOTIVATION: dependency_manager.v's own first_ready_idx scan (a
// serial for-loop overwriting a register variable across up to
// N_NODES=1024 iterations) is the SAME architectural anti-pattern
// already found and fixed twice elsewhere in this project (ERR-0027,
// neural_director.v's free-slot scan; ERR-0028, activation_fill_
// ctrl's max-tree; ERR-0029, sdram_unified_backend.v's W-cache hit-
// index) -- a data-dependent sequential overwrite that forces a
// SERIAL dependency chain across every iteration, even though the
// result does not logically require one. Those three fixes used a
// flat one-hot compare + single-level casez priority-encode, correct
// and efficient for their own small widths (4-8 entries). N_NODES can
// be up to 1024 in this project's own real configs -- a single flat
// casez at that width is impractical to hand-write and not guaranteed
// to synthesize as a balanced tree. This module generalizes the SAME
// underlying principle (no serial dependency chain) to arbitrary
// width via recursive halving: each half is encoded independently
// and in parallel (no dependency between them), and only the FINAL
// combine step (low_valid ? low_result : high_result) depends on
// both halves -- giving real O(log2(WIDTH)) depth instead of O(WIDTH).
//
// Semantics: idx = index of the LOWEST set bit in `in` (bit 0 has
// highest priority), valid = |in. This matches dependency_manager.v's
// own original scan exactly: it iterates ri from N_NODES-1 DOWN TO 0,
// unconditionally overwriting first_ready_idx on every match -- the
// LAST (i.e. lowest-index) match therefore wins, not the first one
// found during the loop's own execution order.
// ============================================================
module priority_encoder_lsb #(
parameter WIDTH = 16,
parameter IDXW = (WIDTH <= 1) ? 1 : $clog2(WIDTH)
)(
input wire [WIDTH-1:0] in,
output wire [IDXW-1:0] idx,
output wire valid
);
generate
if (WIDTH <= 1) begin : GEN_BASE
assign valid = in[0];
assign idx = {IDXW{1'b0}};
end else begin : GEN_SPLIT
localparam LOW_W = WIDTH/2;
localparam HIGH_W = WIDTH - LOW_W;
localparam LOW_IDXW = (LOW_W <= 1) ? 1 : $clog2(LOW_W);
localparam HIGH_IDXW = (HIGH_W <= 1) ? 1 : $clog2(HIGH_W);
wire [LOW_IDXW-1:0] low_idx;
wire low_valid;
wire [HIGH_IDXW-1:0] high_idx;
wire high_valid;
priority_encoder_lsb #(.WIDTH(LOW_W)) u_low (
.in(in[LOW_W-1:0]), .idx(low_idx), .valid(low_valid)
);
priority_encoder_lsb #(.WIDTH(HIGH_W)) u_high (
.in(in[WIDTH-1:LOW_W]), .idx(high_idx), .valid(high_valid)
);
assign valid = low_valid | high_valid;
assign idx = low_valid
? {{(IDXW-LOW_IDXW){1'b0}}, low_idx}
: ({{(IDXW-HIGH_IDXW){1'b0}}, high_idx} + LOW_W[IDXW-1:0]);
end
endgenerate
endmodule
@@ -0,0 +1,150 @@
`timescale 1ns/1ps
// ============================================================
// EXP-0056 -- bit-exact equivalence check: dependency_manager.v
// (baseline) vs dependency_manager_fast.v (priority_encoder_lsb.v
// fix), at N_NODES=1024 -- the REAL config this project's own
// D-Stress benchmark uses, not the small hand-crafted DAG the
// original tb_dependency_manager.v exercises. Both DUTs driven by the
// IDENTICAL random stimulus every cycle (registration + producer-done
// events), every output compared cycle-by-cycle. Pure random
// (required/producer_ids need not form a realistic DAG -- this
// module's own behavior is well-defined for ANY input sequence, and
// bit-exact equivalence for ANY sequence is exactly the property that
// needs proving here).
// ============================================================
module tb;
localparam N_NODES = 1024;
localparam MAX_DEPS = 4;
localparam ADDR_WIDTH = 26;
localparam NODE_IDW = $clog2(N_NODES);
reg clk, rst;
initial begin clk = 0; forever #5 clk = ~clk; end
reg reg_valid;
reg [NODE_IDW-1:0] reg_node_id;
reg [$clog2(MAX_DEPS+1)-1:0] reg_required;
reg [MAX_DEPS*NODE_IDW-1:0] reg_producer_ids;
reg [ADDR_WIDTH-1:0] reg_x_base, reg_w_base, reg_result_addr;
reg [15:0] reg_n_tiles;
reg producer_done_valid;
reg [NODE_IDW-1:0] producer_done_node_id;
reg ready_ready;
wire reg_ready_a, reg_ready_b;
wire ready_valid_a, ready_valid_b;
wire [NODE_IDW-1:0] ready_node_id_a, ready_node_id_b;
wire [ADDR_WIDTH-1:0] ready_x_base_a, ready_x_base_b;
wire [ADDR_WIDTH-1:0] ready_w_base_a, ready_w_base_b;
wire [15:0] ready_n_tiles_a, ready_n_tiles_b;
wire [ADDR_WIDTH-1:0] ready_result_addr_a, ready_result_addr_b;
wire any_pending_a, any_pending_b;
dependency_manager #(
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
) dut_base (
.clk(clk), .rst(rst),
.reg_valid(reg_valid), .reg_ready(reg_ready_a), .reg_node_id(reg_node_id),
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
.reg_result_addr(reg_result_addr),
.producer_done_valid(producer_done_valid), .producer_done_node_id(producer_done_node_id),
.ready_valid(ready_valid_a), .ready_ready(ready_ready), .ready_node_id(ready_node_id_a),
.ready_x_base(ready_x_base_a), .ready_w_base(ready_w_base_a),
.ready_n_tiles(ready_n_tiles_a), .ready_result_addr(ready_result_addr_a),
.any_pending(any_pending_a)
);
dependency_manager_fast #(
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
) dut_fast (
.clk(clk), .rst(rst),
.reg_valid(reg_valid), .reg_ready(reg_ready_b), .reg_node_id(reg_node_id),
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
.reg_result_addr(reg_result_addr),
.producer_done_valid(producer_done_valid), .producer_done_node_id(producer_done_node_id),
.ready_valid(ready_valid_b), .ready_ready(ready_ready), .ready_node_id(ready_node_id_b),
.ready_x_base(ready_x_base_b), .ready_w_base(ready_w_base_b),
.ready_n_tiles(ready_n_tiles_b), .ready_result_addr(ready_result_addr_b),
.any_pending(any_pending_b)
);
integer errors, tests, cyc;
integer seed, i;
integer next_id;
reg [NODE_IDW-1:0] rnd_id;
task automatic check_equal;
begin
tests = tests + 1;
if (reg_ready_a !== reg_ready_b || ready_valid_a !== ready_valid_b ||
any_pending_a !== any_pending_b ||
(ready_valid_a && (ready_node_id_a !== ready_node_id_b ||
ready_x_base_a !== ready_x_base_b ||
ready_w_base_a !== ready_w_base_b ||
ready_n_tiles_a !== ready_n_tiles_b ||
ready_result_addr_a !== ready_result_addr_b))) begin
$display("FAIL @cycle %0d: base(reg_ready=%b ready_valid=%b node=%0d any_pending=%b) fast(reg_ready=%b ready_valid=%b node=%0d any_pending=%b)",
cyc, reg_ready_a, ready_valid_a, ready_node_id_a, any_pending_a,
reg_ready_b, ready_valid_b, ready_node_id_b, any_pending_b);
errors = errors + 1;
end
end
endtask
always @(posedge clk) if (!rst) cyc <= cyc + 1;
initial begin
errors = 0; tests = 0; cyc = 0; seed = 32'hFEEDFACE;
rst = 1; reg_valid = 0; reg_node_id = 0; reg_required = 0; reg_producer_ids = 0;
reg_x_base = 0; reg_w_base = 0; reg_n_tiles = 0; reg_result_addr = 0;
producer_done_valid = 0; producer_done_node_id = 0;
ready_ready = 1;
repeat(5) @(posedge clk);
rst = 0;
$display("=== random stimulus, N_NODES=1024, 20000 cycles ===");
next_id = 0;
for (i = 0; i < 20000; i = i + 1) begin
@(posedge clk);
#1; // let combinational outputs settle before sampling/comparing
// registration: ~15% of cycles, sequential node_id (avoids
// double-registering the same id, which the module itself
// does not need to tolerate -- caller's own responsibility,
// same as the real Director/graph-loader upstream)
reg_valid = (($random(seed) % 100) < 15) && (next_id < N_NODES);
if (reg_valid) begin
reg_node_id = next_id[NODE_IDW-1:0];
reg_required = $random(seed) % (MAX_DEPS+1);
reg_x_base = $random(seed);
reg_w_base = $random(seed);
reg_n_tiles = $random(seed);
reg_result_addr = $random(seed);
reg_producer_ids = {$random(seed), $random(seed)}; // random bits, need not be a valid/realistic producer graph
next_id = next_id + 1;
end
// producer-done: ~10% of cycles, random already-issued id
producer_done_valid = (($random(seed) % 100) < 10) && (next_id > 0);
if (producer_done_valid) begin
rnd_id = ($random(seed) % next_id);
producer_done_node_id = rnd_id;
end
// ready_ready: randomly withhold backpressure sometimes,
// to exercise the "ready_valid held, not yet accepted" path
ready_ready = (($random(seed) % 100) < 80);
check_equal;
end
$display("=== %0d/%0d cycles matched, %0d mismatches ===", tests-errors, tests, errors);
if (errors == 0) $display("ALL TESTS PASSED (tb_dependency_manager_fast, bit-exact vs baseline)");
$finish;
end
endmodule
@@ -0,0 +1,260 @@
`timescale 1ns/1ps
// ============================================================
// EXP-0057 -- real measured comparison: weight-stationary layer reuse
// (layer_weight_buffer.v, double-buffered, background-prefetched)
// vs the zero-reuse D-Stress-style pattern (every read is its own
// independent external fetch), BOTH driven through the SAME real,
// already-verified open-row SDR SDRAM controller (sdram_controller_
// openrow.v, EXP-0054) and behavioral chip model (sdram_model.v) --
// no DDR3, no clock change, the exact hardware this project already
// has. Answers directly: does weight reuse alone (no new memory
// hardware) close enough of the gap that DDR3 stops being necessary
// for a workload class that actually has reuse (e.g. a CNN layer),
// as opposed to D-Stress's own deliberately zero-reuse worst case?
//
// SAME total useful-byte-consumption for both cases (fair
// comparison): L=16 "layers" x M=16 reuses x LAYER_DEPTH=128 bytes =
// 32768 total byte-reads -- identical to D-Stress's own 256x128=32768
// total bytes this whole project has been benchmarked against all
// session.
// REUSE case: L x LAYER_DEPTH = 2048 bytes actually fetched from
// SDRAM (each layer's weights fetched ONCE, reused
// M times from the local double buffer).
// ZERO-REUSE case: L x M x LAYER_DEPTH = 32768 bytes fetched (every
// single read is independent, matching D-Stress).
// ============================================================
module tb;
localparam BURST_LEN = 8;
localparam ROW_BITS = 13;
localparam COL_BITS = 10;
localparam BANK_BITS = 2;
localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS;
localparam ALIGN_BITS = $clog2(BURST_LEN);
localparam CLK_FREQ_MHZ = 64;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
localparam LAYER_DEPTH = 128; // bytes/layer weight block (matches P_IN*MAX_TILES=8*16 tile convention)
localparam L = 16; // number of layers
localparam M = 16; // reuses per layer (e.g. spatial positions a filter slides across)
localparam WORDS_PER_LAYER = LAYER_DEPTH/2; // sdram_controller word=16-bit
localparam BURSTS_PER_LAYER = LAYER_DEPTH/(2*BURST_LEN); // 16-byte (8-word) transactions per layer
reg clk = 0;
always #(CLK_PERIOD_NS/2.0) clk = ~clk;
reg rst;
integer cyc;
always @(posedge clk) if (!rst) cyc <= cyc + 1;
// ================= shared physical SDRAM (real open-row controller) =================
reg ctrl_req, ctrl_wr;
reg [ADDR_WIDTH-1:0] ctrl_addr;
reg [16*BURST_LEN-1:0] ctrl_wdata;
reg [2*BURST_LEN-1:0] ctrl_wmask;
wire [16*BURST_LEN-1:0] ctrl_rdata;
wire ctrl_ready, ctrl_busy;
wire cke, cs_n, ras_n, cas_n, we_n;
wire [BANK_BITS-1:0] ba;
wire [ROW_BITS-1:0] a;
wire [15:0] dq;
wire [1:0] dqm;
sdram_controller_openrow #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN),
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_ctrl (
.clk(clk), .rst(rst),
.req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask),
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy),
.sdram_cke(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n),
.sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm)
);
sdram_model #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_mem (
.clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n),
.ba(ba), .a(a), .dq(dq), .dqm(dqm)
);
task automatic sdram_write_burst(input [ADDR_WIDTH-1:0] word_addr, input [16*BURST_LEN-1:0] data);
begin
@(posedge clk); while (ctrl_busy) @(posedge clk);
ctrl_req = 1'b1; ctrl_wr = 1'b1; ctrl_addr = word_addr; ctrl_wdata = data; ctrl_wmask = {(2*BURST_LEN){1'b0}};
@(posedge clk); ctrl_req = 1'b0;
while (!ctrl_ready) @(posedge clk);
end
endtask
task automatic sdram_read_burst(input [ADDR_WIDTH-1:0] word_addr, output [16*BURST_LEN-1:0] data);
begin
@(posedge clk); while (ctrl_busy) @(posedge clk);
ctrl_req = 1'b1; ctrl_wr = 1'b0; ctrl_addr = word_addr; ctrl_wmask = {(2*BURST_LEN){1'b0}};
@(posedge clk); ctrl_req = 1'b0;
while (!ctrl_ready) @(posedge clk);
data = ctrl_rdata;
end
endtask
// ================= layer_weight_buffer under test =================
reg fill_we;
reg [$clog2(LAYER_DEPTH)-1:0] fill_addr;
reg [7:0] fill_data;
reg fill_done;
reg [$clog2(LAYER_DEPTH)-1:0] rd_addr;
wire [7:0] rd_data;
reg consume_done;
wire active_sel, swapped;
layer_weight_buffer #(.DATA_WIDTH(8), .LAYER_DEPTH(LAYER_DEPTH)) u_lwb (
.clk(clk), .rst(rst),
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data), .fill_done(fill_done),
.rd_addr(rd_addr), .rd_data(rd_data), .consume_done(consume_done),
.active_sel(active_sel), .swapped(swapped)
);
integer errors, tests;
// pre-load SDRAM with L distinct layer patterns, at word address layer_idx*WORDS_PER_LAYER
task automatic preload_sdram_layers;
integer li, bi;
reg [16*BURST_LEN-1:0] burst_data;
integer wb;
begin
for (li = 0; li < L; li = li + 1) begin
for (bi = 0; bi < BURSTS_PER_LAYER; bi = bi + 1) begin
for (wb = 0; wb < BURST_LEN; wb = wb + 1)
burst_data[wb*16 +: 16] = {8'(8'h20+li), 8'(bi*BURST_LEN+wb)};
sdram_write_burst((li*WORDS_PER_LAYER + bi*BURST_LEN), burst_data);
end
end
end
endtask
// fetch layer li's weights (bulk sequential, BURSTS_PER_LAYER transactions)
// into the layer_weight_buffer's inactive side
task automatic prefetch_layer(input integer li);
integer bi, wb;
reg [16*BURST_LEN-1:0] burst_data;
begin
for (bi = 0; bi < BURSTS_PER_LAYER; bi = bi + 1) begin
sdram_read_burst((li*WORDS_PER_LAYER + bi*BURST_LEN), burst_data);
for (wb = 0; wb < BURST_LEN; wb = wb + 1) begin
@(posedge clk);
fill_we = 1'b1;
fill_addr = (bi*BURST_LEN + wb) & (2*BURST_LEN-1) | (bi*2*BURST_LEN); // byte index within layer
fill_addr = bi*(2*BURST_LEN) + wb*2; // low byte of word wb
fill_data = burst_data[wb*16 +: 8];
@(posedge clk);
fill_addr = bi*(2*BURST_LEN) + wb*2 + 1; // high byte of word wb
fill_data = burst_data[wb*16+8 +: 8];
end
end
@(posedge clk); fill_we = 1'b0;
fill_done = 1'b1; @(posedge clk); fill_done = 1'b0;
end
endtask
task automatic consume_layer_check(input integer li, input integer errors_before, output integer errors_after);
integer r, k;
reg [7:0] expected;
begin
errors_after = errors_before;
for (r = 0; r < M; r = r + 1) begin
for (k = 0; k < LAYER_DEPTH; k = k + 1) begin
rd_addr = k[$clog2(LAYER_DEPTH)-1:0];
#1;
tests = tests + 1;
expected = 8'(8'h20+li) ; // high byte of the 16-bit word pattern for even k, low byte pattern for odd k -- see preload
// preload packed {8'h20+li, byte_idx} per WORD (16-bit): low byte = byte_idx, high byte = 8'h20+li
if (k[0] == 1'b0) expected = {1'b0, k[7:1]}; // low byte of word = WORD index (bi*BURST_LEN+wb), i.e. k/2 -- see preload_sdram_layers
else expected = 8'(8'h20+li); // high byte of word = layer tag
if (rd_data !== expected) begin
$display("FAIL layer=%0d reuse=%0d k=%0d: expected %h got %h", li, r, k, expected, rd_data);
errors_after = errors_after + 1;
end
@(posedge clk);
end
end
consume_done = 1'b1; @(posedge clk); consume_done = 1'b0;
end
endtask
integer li_i;
integer t0, total_cycles_reuse, total_cycles_zeroreuse;
// zero-reuse baseline: L*M independent reads, each LAYER_DEPTH bytes,
// NO local buffering -- every single "reuse" goes straight to SDRAM,
// matching D-Stress's own access pattern exactly (through the SAME
// real open-row controller).
task automatic zero_reuse_baseline;
integer li, r, bi;
reg [16*BURST_LEN-1:0] junk;
begin
for (li = 0; li < L; li = li + 1) begin
for (r = 0; r < M; r = r + 1) begin
for (bi = 0; bi < BURSTS_PER_LAYER; bi = bi + 1) begin
sdram_read_burst((li*WORDS_PER_LAYER + bi*BURST_LEN), junk);
end
end
end
end
endtask
initial begin
errors = 0; tests = 0; cyc = 0;
rst = 1; ctrl_req = 0; ctrl_wr = 0; ctrl_addr = 0; ctrl_wdata = 0; ctrl_wmask = 0;
fill_we = 0; fill_addr = 0; fill_data = 0; fill_done = 0; rd_addr = 0; consume_done = 0;
repeat(5) @(posedge clk);
rst = 0;
@(posedge clk); while (ctrl_busy) @(posedge clk);
$display("=== preload SDRAM with %0d distinct layer patterns ===", L);
preload_sdram_layers;
$display("=== REUSE case correctness pass: %0d layers x %0d reuses, double-buffered background prefetch (data check only, not timed) ===", L, M);
prefetch_layer(0);
consume_done = 1'b1; @(posedge clk); consume_done = 1'b0; // trigger initial swap
for (li_i = 0; li_i < L; li_i = li_i + 1) begin
fork
consume_layer_check(li_i, errors, errors);
begin
if (li_i+1 < L) prefetch_layer(li_i+1);
end
join
end
$display(" correctness: %0d/%0d, %0d errors", tests-errors, tests, errors);
// ---- FAIR timing comparison: measure ONLY the real SDRAM
// fetch cost in each case (the actual question this benchmark
// exists to answer -- how much does reuse reduce dependence on
// external memory bandwidth). Compute-side consumption cost is
// deliberately excluded from BOTH measurements here -- it is
// identical in both cases (same neural_processor.v pipeline
// rate regardless of where weights come from) and including it
// asymmetrically was a real bug in an earlier version of this
// testbench (see EXP-0057 writeup). ----
$display("=== REUSE case: pure SDRAM fetch time for %0d layers (%0d bytes total) ===", L, L*LAYER_DEPTH);
@(posedge clk); while (ctrl_busy) @(posedge clk);
t0 = cyc;
for (li_i = 0; li_i < L; li_i = li_i + 1) prefetch_layer(li_i);
total_cycles_reuse = cyc - t0;
$display(" REUSE: %0d cycles to fetch %0d bytes from SDRAM (each layer fetched ONCE, reused %0d x locally)",
total_cycles_reuse, L*LAYER_DEPTH, M);
$display("=== ZERO-REUSE baseline: pure SDRAM fetch time for %0d bytes (every reuse fetched independently) ===", L*M*LAYER_DEPTH);
@(posedge clk); while (ctrl_busy) @(posedge clk);
t0 = cyc;
zero_reuse_baseline;
total_cycles_zeroreuse = cyc - t0;
$display(" ZERO-REUSE: %0d cycles to fetch %0d bytes from SDRAM",
total_cycles_zeroreuse, L*M*LAYER_DEPTH);
$display("=== RESULT ===");
$display(" REUSE case data correctness: %0d/%0d, %0d errors", tests-errors, tests, errors);
$display(" REAL measured speedup from weight reuse alone (SAME hardware, no DDR3, no clock change): %0f x",
total_cycles_zeroreuse * 1.0 / total_cycles_reuse);
if (errors == 0) $display("ALL DATA CHECKS PASSED (tb_layer_reuse_vs_zero_reuse)");
$finish;
end
endmodule
+141
View File
@@ -0,0 +1,141 @@
`timescale 1ns/1ps
// ============================================================
// EXP-0057 -- isolated correctness check for layer_weight_buffer.v:
// fill buffer A, swap, read A many times while filling B, swap again
// (order-independent: sometimes fill_done arrives first, sometimes
// consume_done does), verify data integrity and correct buffer
// selection throughout.
// ============================================================
module tb;
localparam DATA_WIDTH = 8;
localparam LAYER_DEPTH = 128;
localparam ADDRW = $clog2(LAYER_DEPTH);
reg clk = 0;
always #5 clk = ~clk;
reg rst;
reg fill_we;
reg [ADDRW-1:0] fill_addr;
reg [DATA_WIDTH-1:0] fill_data;
reg fill_done;
reg [ADDRW-1:0] rd_addr;
wire [DATA_WIDTH-1:0] rd_data;
reg consume_done;
wire active_sel;
wire swapped;
layer_weight_buffer #(
.DATA_WIDTH(DATA_WIDTH), .LAYER_DEPTH(LAYER_DEPTH)
) dut (
.clk(clk), .rst(rst),
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data), .fill_done(fill_done),
.rd_addr(rd_addr), .rd_data(rd_data), .consume_done(consume_done),
.active_sel(active_sel), .swapped(swapped)
);
integer errors, tests, i;
task automatic fill_layer(input [7:0] pattern_base);
integer k;
begin
for (k = 0; k < LAYER_DEPTH; k = k + 1) begin
@(posedge clk);
fill_we = 1'b1; fill_addr = k[ADDRW-1:0]; fill_data = pattern_base + k[7:0];
end
@(posedge clk);
fill_we = 1'b0;
fill_done = 1'b1;
@(posedge clk);
fill_done = 1'b0;
end
endtask
task automatic read_and_check_layer(input [7:0] pattern_base, input integer n_reuses);
integer r, k;
begin
for (r = 0; r < n_reuses; r = r + 1) begin
for (k = 0; k < LAYER_DEPTH; k = k + 1) begin
rd_addr = k[ADDRW-1:0];
#1;
tests = tests + 1;
if (rd_data !== (pattern_base + k[7:0])) begin
$display("FAIL reuse=%0d addr=%0d: expected %0d got %0d", r, k, pattern_base+k[7:0], rd_data);
errors = errors + 1;
end
@(posedge clk);
end
end
consume_done = 1'b1;
@(posedge clk);
consume_done = 1'b0;
end
endtask
initial begin
errors = 0; tests = 0;
rst = 1; fill_we = 0; fill_addr = 0; fill_data = 0; fill_done = 0;
rd_addr = 0; consume_done = 0;
repeat(3) @(posedge clk);
rst = 0;
$display("=== fill layer 0 (pattern 0x10), swap in ===");
fill_layer(8'h10);
if (active_sel !== 1'b0) begin
$display("FAIL: expected active_sel=0 before any swap (fill alone must not swap)");
errors = errors + 1;
end
// consume_done from reset state (never asserted yet) + fill_done just latched -> not swapped yet
// now assert consume_done once (simulating "nothing to consume yet, first layer") to trigger the swap
consume_done = 1'b1; @(posedge clk); consume_done = 1'b0;
@(posedge clk); #1; // swap logic is 2-cycle latency from the triggering pulse; let it settle
if (active_sel !== 1'b1) begin
$display("FAIL: expected active_sel=1 after first swap, got %b", active_sel);
errors = errors + 1;
end
tests = tests + 1;
$display("=== read layer 0 (now active, 5 reuses), meanwhile fill layer 1 (pattern 0x40) ===");
fork
read_and_check_layer(8'h10, 5);
fill_layer(8'h40);
join
@(posedge clk); #1;
if (active_sel !== 1'b0) begin
$display("FAIL: expected active_sel=0 after second swap (back to buffer 0, now holding layer1 data), got %b", active_sel);
errors = errors + 1;
end
tests = tests + 1;
$display("=== read layer 1 (pattern 0x40, 3 reuses), meanwhile fill layer 2 (pattern 0x80) -- fill finishes FIRST this time ===");
fork
begin
fill_layer(8'h80);
end
begin
#50; // let fill get a head start, so fill_done lands before consume_done
read_and_check_layer(8'h40, 3);
end
join
@(posedge clk); #1;
if (active_sel !== 1'b1) begin
$display("FAIL: expected active_sel=1 after third swap, got %b", active_sel);
errors = errors + 1;
end
tests = tests + 1;
$display("=== read layer 2 (pattern 0x80, 4 reuses), verify final data ===");
read_and_check_layer(8'h80, 4);
consume_done = 1'b1; @(posedge clk); consume_done = 1'b0; // extra pulse, no fill pending: must NOT swap without a fill_done
if (active_sel !== 1'b1) begin
$display("FAIL: consume_done alone (no matching fill_done) must not cause a swap, got active_sel=%b", active_sel);
errors = errors + 1;
end
tests = tests + 1;
$display("=== %0d/%0d passed, %0d errors ===", tests-errors, tests, errors);
if (errors == 0) $display("ALL TESTS PASSED (tb_layer_weight_buffer)");
$finish;
end
endmodule
+72
View File
@@ -0,0 +1,72 @@
`timescale 1ns/1ps
// ============================================================
// EXP-0056 -- isolated correctness check for the N_SLOTS==16
// balanced max-tree added to nms_activation_fill_ctrl_v3_n16.v,
// against the SAME flat-scan reference the original module's own
// GEN_MAXTREE_FALLBACK uses (the two must always agree -- that
// fallback path is explicitly documented as "correct but not
// optimized", i.e. the golden reference for what ANY replacement
// must compute). Random 16-way max over 10000 vectors.
// ============================================================
module tb;
reg [15:0] v [0:15];
integer i, k, seed, errors, tests;
reg [15:0] ref_max;
// exact mirror of nms_activation_fill_ctrl_v3_n16.v's own
// GEN_MAXTREE_N16 combinational tree
wire [15:0] m0 = (v[0] > v[1]) ? v[0] : v[1];
wire [15:0] m1 = (v[2] > v[3]) ? v[2] : v[3];
wire [15:0] m2 = (v[4] > v[5]) ? v[4] : v[5];
wire [15:0] m3 = (v[6] > v[7]) ? v[6] : v[7];
wire [15:0] m4 = (v[8] > v[9]) ? v[8] : v[9];
wire [15:0] m5 = (v[10] > v[11]) ? v[10] : v[11];
wire [15:0] m6 = (v[12] > v[13]) ? v[12] : v[13];
wire [15:0] m7 = (v[14] > v[15]) ? v[14] : v[15];
wire [15:0] m01 = (m0 > m1) ? m0 : m1;
wire [15:0] m23 = (m2 > m3) ? m2 : m3;
wire [15:0] m45 = (m4 > m5) ? m4 : m5;
wire [15:0] m67 = (m6 > m7) ? m6 : m7;
wire [15:0] m0123 = (m01 > m23) ? m01 : m23;
wire [15:0] m4567 = (m45 > m67) ? m45 : m67;
wire [15:0] max_final = (m0123 > m4567) ? m0123 : m4567;
initial begin
errors = 0; tests = 0; seed = 32'hA5A5F00D;
// targeted: all-zero, max at every single position
for (k = 0; k < 16; k = k + 1) v[k] = 16'h0;
#1; tests = tests + 1;
if (max_final !== 16'h0) begin
$display("FAIL all-zero: got %0d", max_final); errors = errors + 1;
end
for (i = 0; i < 16; i = i + 1) begin
for (k = 0; k < 16; k = k + 1) v[k] = 16'h1;
v[i] = 16'hFFFF;
#1; tests = tests + 1;
if (max_final !== 16'hFFFF) begin
$display("FAIL max-at-%0d: got %0d", i, max_final); errors = errors + 1;
end
end
// random
for (i = 0; i < 10000; i = i + 1) begin
ref_max = 16'h0;
for (k = 0; k < 16; k = k + 1) begin
v[k] = $random(seed);
if (v[k] > ref_max) ref_max = v[k];
end
#1;
tests = tests + 1;
if (max_final !== ref_max) begin
$display("FAIL random iter=%0d: expected %0d got %0d", i, ref_max, max_final);
errors = errors + 1;
end
end
$display("=== %0d/%0d passed, %0d errors ===", tests-errors, tests, errors);
if (errors == 0) $display("ALL TESTS PASSED (tb_maxtree_n16)");
$finish;
end
endmodule
+133
View File
@@ -0,0 +1,133 @@
`timescale 1ns/1ps
// ============================================================
// EXP-0056 -- isolated correctness check for priority_encoder_lsb.v
// against a trivial behavioral reference (linear scan, allowed to be
// slow since it is testbench-only), at both a small width (16,
// dependency_manager.v's own module default) and the real large width
// this fix targets (1024, the real N_NODES this project actually
// uses for D-Stress). Exhaustive at WIDTH=16 (65536 patterns), random
// at WIDTH=1024 (exhaustive is infeasible: 2^1024 patterns).
// ============================================================
module tb;
localparam W_SMALL = 16;
localparam IDXW_SMALL = $clog2(W_SMALL);
reg [W_SMALL-1:0] in_small;
wire [IDXW_SMALL-1:0] idx_small;
wire valid_small;
priority_encoder_lsb #(.WIDTH(W_SMALL)) dut_small (
.in(in_small), .idx(idx_small), .valid(valid_small)
);
localparam W_BIG = 1024;
localparam IDXW_BIG = $clog2(W_BIG);
reg [W_BIG-1:0] in_big;
wire [IDXW_BIG-1:0] idx_big;
wire valid_big;
priority_encoder_lsb #(.WIDTH(W_BIG)) dut_big (
.in(in_big), .idx(idx_big), .valid(valid_big)
);
function automatic integer ref_lowest_set_bit(input [W_BIG-1:0] v, input integer width);
integer k;
begin
ref_lowest_set_bit = -1;
for (k = width-1; k >= 0; k = k - 1)
if (v[k]) ref_lowest_set_bit = k;
end
endfunction
integer errors, tests;
integer i, ref_idx;
integer seed;
initial begin
errors = 0; tests = 0; seed = 32'hDEC0DE;
$display("=== TEST 1: WIDTH=16, exhaustive (65536 patterns) ===");
for (i = 0; i < 65536; i = i + 1) begin
in_small = i[W_SMALL-1:0];
#1;
ref_idx = ref_lowest_set_bit(i[W_BIG-1:0], W_SMALL);
tests = tests + 1;
if (ref_idx == -1) begin
if (valid_small !== 1'b0) begin
$display("FAIL pattern=%b: expected valid=0, got valid=%b", in_small, valid_small);
errors = errors + 1;
end
end else begin
if (valid_small !== 1'b1 || idx_small !== ref_idx[IDXW_SMALL-1:0]) begin
$display("FAIL pattern=%b: expected idx=%0d valid=1, got idx=%0d valid=%b",
in_small, ref_idx, idx_small, valid_small);
errors = errors + 1;
end
end
end
$display(" TEST 1: %0d/%0d passed", tests-errors, tests);
$display("=== TEST 2: WIDTH=1024, targeted + random (10000 patterns) ===");
// targeted: all-zero, single-bit at every position, all-ones
in_big = {W_BIG{1'b0}};
#1;
tests = tests + 1;
if (valid_big !== 1'b0) begin
$display("FAIL all-zero: expected valid=0, got valid=%b", valid_big);
errors = errors + 1;
end
for (i = 0; i < W_BIG; i = i + 1) begin
in_big = {W_BIG{1'b0}};
in_big[i] = 1'b1;
#1;
tests = tests + 1;
if (valid_big !== 1'b1 || idx_big !== i[IDXW_BIG-1:0]) begin
$display("FAIL single-bit@%0d: expected idx=%0d valid=1, got idx=%0d valid=%b",
i, i, idx_big, valid_big);
errors = errors + 1;
end
end
in_big = {W_BIG{1'b1}};
#1;
ref_idx = ref_lowest_set_bit(in_big, W_BIG);
tests = tests + 1;
if (valid_big !== 1'b1 || idx_big !== ref_idx[IDXW_BIG-1:0]) begin
$display("FAIL all-ones: expected idx=%0d valid=1, got idx=%0d valid=%b",
ref_idx, idx_big, valid_big);
errors = errors + 1;
end
// random
for (i = 0; i < 10000; i = i + 1) begin
in_big = {$random(seed), $random(seed), $random(seed), $random(seed),
$random(seed), $random(seed), $random(seed), $random(seed),
$random(seed), $random(seed), $random(seed), $random(seed),
$random(seed), $random(seed), $random(seed), $random(seed),
$random(seed), $random(seed), $random(seed), $random(seed),
$random(seed), $random(seed), $random(seed), $random(seed),
$random(seed), $random(seed), $random(seed), $random(seed),
$random(seed), $random(seed), $random(seed), $random(seed)};
#1;
ref_idx = ref_lowest_set_bit(in_big, W_BIG);
tests = tests + 1;
if (ref_idx == -1) begin
if (valid_big !== 1'b0) begin
$display("FAIL random iter=%0d: expected valid=0, got valid=%b", i, valid_big);
errors = errors + 1;
end
end else begin
if (valid_big !== 1'b1 || idx_big !== ref_idx[IDXW_BIG-1:0]) begin
$display("FAIL random iter=%0d: expected idx=%0d valid=1, got idx=%0d valid=%b",
i, ref_idx, idx_big, valid_big);
errors = errors + 1;
end
end
end
$display(" TEST 2: %0d/%0d passed", tests-errors, tests);
$display("=== %0d/%0d total, %0d errors ===", tests-errors, tests, errors);
if (errors == 0) $display("ALL TESTS PASSED (tb_priority_encoder_lsb)");
$finish;
end
endmodule