Files
FPGA-Neural/hardware/v3/rtl/sdram_slot_arbiter2.v
T
micheleandClaude Sonnet 5 09fbf01ea5 feat: first genuine N=2 multi-core system, two real bugs found+fixed (EXP-0066)
New sdram_slot_arbiter2.v + tb_np_director_n2_system.v: real
neural_director_packed.v dispatching to 2 real packed_slot.v
instances sharing one real SDRAM controller. Jobs submitted one at a
time through the Director's own producer interface -- the Director's
own scheduling decisions determine slot assignment here, unlike every
prior V3 test.

Bug 1 (real, structural): the arbiter's first design registered its
grant one cycle late; layer_prefetch_ctrl.v's ctrl_req is a one-shot
pulse with no retry (every prior use wired it directly to a
controller, never behind arbitration), so a slot's first request
could be silently lost, hanging it forever. Fixed with a new
S_MEMWAIT state in packed_slot.v (wait for a combinational mem_grant
before ever pulsing layer_prefetch_ctrl's start) and a combinational-
first grant in the arbiter.

Bug 2 (testbench): node_id used a stray bit-slice (li[15:8]) instead
of a real multiply, making every layer produce the same node_ids and
silently checking results against the wrong layer's golden value.
Fixed.

Result: 12/12 PASS, 0 errors, real concurrent execution across both
slots (slot 0: positions {0,1,4,5,8,9}, slot 1: {2,3,6,7,10,11}).

Also noted (user correction): the SDR SDRAM controller used
throughout this memory path is a declared placeholder -- the real
target is DDR3 on a custom XC7A100T board, not yet built.

Full writeup in hardware/v2/logs/experiments.log EXP-0066.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-17 00:16:47 +02:00

116 lines
5.0 KiB
Verilog

`timescale 1ns/1ps
// ============================================================
// V3 -- 2-way arbiter between packed_slot.v's own weight-fetch ctrl
// port and ONE real, shared sdram_controller.v.
//
// Grants LOCK for the whole duration of a slot's mem_active (its
// entire multi-burst layer fetch), not per-transaction -- a slot's
// own layer_prefetch_ctrl.v issues MANY back-to-back ctrl_req bursts
// per fetch, and interleaving those with the OTHER slot's bursts
// would corrupt both (neither is designed to have its own multi-burst
// sequence interrupted mid-flight). First-active-wins priority; the
// other slot's ctrl_ready is held at 0 (never pulses) while not
// granted, so its own req/ready FSM simply waits, harmlessly, exactly
// like it already does for ordinary controller busy cycles.
// ============================================================
module sdram_slot_arbiter2 #(
parameter ADDR_WIDTH = 25,
parameter BURST_LEN = 8
)(
input wire clk,
input wire rst,
input wire slot0_active,
output wire slot0_grant,
input wire slot0_req,
input wire slot0_wr,
input wire [ADDR_WIDTH-1:0] slot0_addr,
input wire [16*BURST_LEN-1:0] slot0_wdata,
input wire [2*BURST_LEN-1:0] slot0_wmask,
output wire [16*BURST_LEN-1:0] slot0_rdata,
output wire slot0_ready,
output wire slot0_busy,
input wire slot1_active,
output wire slot1_grant,
input wire slot1_req,
input wire slot1_wr,
input wire [ADDR_WIDTH-1:0] slot1_addr,
input wire [16*BURST_LEN-1:0] slot1_wdata,
input wire [2*BURST_LEN-1:0] slot1_wmask,
output wire [16*BURST_LEN-1:0] slot1_rdata,
output wire slot1_ready,
output wire slot1_busy,
output wire ctrl_req,
output wire ctrl_wr,
output wire [ADDR_WIDTH-1:0] ctrl_addr,
output wire [16*BURST_LEN-1:0] ctrl_wdata,
output wire [2*BURST_LEN-1:0] ctrl_wmask,
input wire [16*BURST_LEN-1:0] ctrl_rdata,
input wire ctrl_ready,
input wire ctrl_busy
);
// grant_now is COMBINATIONAL, not registered: layer_prefetch_ctrl.v
// issues ctrl_req as a genuine one-shot pulse (it has only ever
// been used wired DIRECTLY to a controller before this arbiter --
// EXP-0057/58/62/65 -- so it assumes immediate visibility, not a
// registered/one-cycle-late grant). A purely-registered arbiter
// (grant decided AT the clock edge, valid only the FOLLOWING
// cycle) misses that first pulse entirely -- found empirically:
// slot1 hung forever in its own S_WAIT state, ctrl_req correctly
// pulsed for exactly one cycle then dropped, but the registered
// grant hadn't caught up yet, so the real controller never saw it
// and ctrl_ready never came. `locked`/`grant_reg` below only
// LATCH a decision already available combinationally this same
// cycle, purely to keep it sticky once BOTH slots are active
// (prevents switching mid-fetch), never to delay the FIRST grant.
reg locked;
reg grant_reg;
wire grant_now = locked ? grant_reg : (slot0_active ? 1'b0 : 1'b1);
wire either_active = slot0_active || slot1_active;
always @(posedge clk) begin
if (rst) begin
locked <= 1'b0;
grant_reg<= 1'b0;
end else begin
if (!locked) begin
if (either_active) begin
locked <= 1'b1;
grant_reg <= grant_now;
end
end else begin
if (grant_reg == 1'b0 && !slot0_active) locked <= 1'b0;
if (grant_reg == 1'b1 && !slot1_active) locked <= 1'b0;
end
end
end
wire sel0 = either_active && (grant_now == 1'b0);
wire sel1 = either_active && (grant_now == 1'b1);
// combinational grant feedback: a slot must see its OWN grant
// asserted (in response to its own mem_active going high, same
// cycle) before it may pulse layer_prefetch_ctrl.v's one-shot
// ctrl_req -- see packed_slot.v's own S_MEMWAIT state.
assign slot0_grant = sel0;
assign slot1_grant = sel1;
assign ctrl_req = sel0 ? slot0_req : (sel1 ? slot1_req : 1'b0);
assign ctrl_wr = sel0 ? slot0_wr : (sel1 ? slot1_wr : 1'b0);
assign ctrl_addr = sel0 ? slot0_addr : (sel1 ? slot1_addr : {ADDR_WIDTH{1'b0}});
assign ctrl_wdata = sel0 ? slot0_wdata : (sel1 ? slot1_wdata : {(16*BURST_LEN){1'b0}});
assign ctrl_wmask = sel0 ? slot0_wmask : (sel1 ? slot1_wmask : {(2*BURST_LEN){1'b0}});
assign slot0_rdata = ctrl_rdata;
assign slot0_ready = sel0 ? ctrl_ready : 1'b0;
assign slot0_busy = sel0 ? ctrl_busy : 1'b1;
assign slot1_rdata = ctrl_rdata;
assign slot1_ready = sel1 ? ctrl_ready : 1'b0;
assign slot1_busy = sel1 ? ctrl_busy : 1'b1;
endmodule