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:
@@ -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
@@ -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
|
||||
@@ -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_openrow.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
|
||||
@@ -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&®_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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user