#include "online_crossbar.h" #include "crossbar_generator.h" #include "read_crossbar.h" #include #include #include #include #include #include #include #include struct Online_Crossbar { Online_Crossbar_Config config; double *resistances; conductance_mapping mapping; double *state_ring; double *results; size_t published_count; size_t consumed_count; size_t result_count; int started; int simulation_done; int failed; int mutex_initialized; int state_condition_initialized; int progress_condition_initialized; pthread_mutex_t mutex; pthread_cond_t state_available; pthread_cond_t progress; }; static int callback_text(char *text, int ident, void *user_data) { (void)ident; (void)user_data; if (text && (strncmp(text, "stderr", 6) == 0 || strstr(text, "Error") != NULL || strstr(text, "error") != NULL)) fprintf(stderr, "ngspice: %s\n", text); return 0; } static int callback_init_data(pvecinfoall values, int ident, void *user_data) { (void)values; (void)ident; (void)user_data; return 0; } static void fail_locked(Online_Crossbar *crossbar) { crossbar->failed = 1; pthread_cond_broadcast(&crossbar->state_available); pthread_cond_broadcast(&crossbar->progress); } static int callback_exit(int status, NG_BOOL immediate, NG_BOOL quit_exit, int ident, void *user_data) { (void)status; (void)immediate; (void)quit_exit; (void)ident; Online_Crossbar *crossbar = user_data; pthread_mutex_lock(&crossbar->mutex); fail_locked(crossbar); pthread_mutex_unlock(&crossbar->mutex); return 0; } static int callback_background(NG_BOOL running, int ident, void *user_data) { (void)ident; Online_Crossbar *crossbar = user_data; /* libngspice passes false at worker start and true at worker exit. */ if (!running) return 0; pthread_mutex_lock(&crossbar->mutex); crossbar->simulation_done = 1; if (crossbar->result_count < crossbar->config.num_timesteps) fail_locked(crossbar); pthread_cond_broadcast(&crossbar->progress); pthread_cond_broadcast(&crossbar->state_available); pthread_mutex_unlock(&crossbar->mutex); return 0; } static int row_from_name(const char *name, size_t *row_out) { const char *digits = name + strlen(name); while (digits > name && isdigit((unsigned char)digits[-1])) digits--; if (*digits == '\0') return -1; char *end = NULL; unsigned long value = strtoul(digits, &end, 10); if (end == digits || *end != '\0') return -1; *row_out = (size_t)value; return 0; } static size_t timestep_for_time(const Online_Crossbar *crossbar, double time) { if (time <= 0.0) return 0; double scaled = time / crossbar->config.time_step; /* Hold state[t] over the full interval (t*dt, (t+1)*dt]. */ double interval = ceil(scaled - 1.0e-9); size_t timestep = interval <= 1.0 ? 0 : (size_t)interval - 1; if (timestep >= crossbar->config.num_timesteps) timestep = crossbar->config.num_timesteps - 1; return timestep; } static int callback_voltage(double *voltage, double time, char *name, int ident, void *user_data) { (void)ident; Online_Crossbar *crossbar = user_data; size_t row; if (!name || row_from_name(name, &row) != 0 || row >= crossbar->config.num_neurons) { fprintf(stderr, "ngspice: unrecognized external source %s\n", name ? name : "(null)"); return 1; } size_t timestep = timestep_for_time(crossbar, time); pthread_mutex_lock(&crossbar->mutex); while (!crossbar->failed && crossbar->published_count <= timestep) pthread_cond_wait(&crossbar->state_available, &crossbar->mutex); if (crossbar->failed) { pthread_mutex_unlock(&crossbar->mutex); return 1; } *voltage = crossbar->config.spike_amplitude * crossbar->state_ring[(timestep % 2) * crossbar->config.num_neurons + row]; if (crossbar->consumed_count < timestep + 1) { crossbar->consumed_count = timestep + 1; pthread_cond_broadcast(&crossbar->progress); } pthread_mutex_unlock(&crossbar->mutex); return 0; } static int callback_data(pvecvaluesall values, int count, int ident, void *user_data) { (void)count; (void)ident; Online_Crossbar *crossbar = user_data; double time = -1.0; double *columns = calloc(crossbar->config.num_outputs * 2, sizeof(*columns)); unsigned char *found = calloc(crossbar->config.num_outputs * 2, sizeof(*found)); if (!columns || !found) { free(columns); free(found); pthread_mutex_lock(&crossbar->mutex); fail_locked(crossbar); pthread_mutex_unlock(&crossbar->mutex); return 1; } for (int i = 0; i < values->veccount; i++) { pvecvalues value = values->vecsa[i]; if (value->is_scale || strcmp(value->name, "time") == 0) { time = value->creal; continue; } const char *col = strstr(value->name, "col"); if (!col) continue; char *end = NULL; unsigned long index = strtoul(col + 3, &end, 10); if (end != col + 3 && index < crossbar->config.num_outputs * 2) { columns[index] = value->creal; found[index] = 1; } } if (time >= 0.0) { double scaled = time / crossbar->config.time_step; double rounded = nearbyint(scaled); if (fabs(scaled - rounded) <= 1.0e-7 && rounded >= 1.0 && (size_t)rounded <= crossbar->config.num_timesteps) { size_t timestep = (size_t)rounded - 1; int complete = 1; for (size_t i = 0; i < crossbar->config.num_outputs * 2; i++) complete = complete && found[i]; if (complete) { double *decoded = crossbar->results + timestep * crossbar->config.num_outputs; if (convert_output_to_software( crossbar->config.num_neurons, crossbar->config.num_outputs, 1, columns, crossbar->resistances, crossbar->config.load_resistance, &crossbar->mapping, NULL, crossbar->config.spike_amplitude, decoded) == 0) { pthread_mutex_lock(&crossbar->mutex); if (crossbar->result_count < timestep + 1) crossbar->result_count = timestep + 1; if (timestep + 1 < crossbar->config.num_timesteps) ngSpice_SetBkpt( (timestep + 2) * crossbar->config.time_step); pthread_cond_broadcast( &crossbar->progress); pthread_mutex_unlock(&crossbar->mutex); } } } } free(columns); free(found); return 0; } int online_crossbar_init(const Online_Crossbar_Config *config, const spires_reservoir *reservoir, Online_Crossbar **crossbar_out) { if (!config || !reservoir || !crossbar_out || config->num_neurons == 0 || config->num_outputs == 0 || config->num_timesteps == 0 || config->time_step <= 0.0 || config->spike_amplitude == 0.0 || config->load_resistance <= 0.0 || !config->model_path || !config->subcircuit_name || !config->netlist_path) return -1; Online_Crossbar *crossbar = calloc(1, sizeof(*crossbar)); if (!crossbar) return -1; crossbar->config = *config; if (pthread_mutex_init(&crossbar->mutex, NULL) != 0) goto fail; crossbar->mutex_initialized = 1; if (pthread_cond_init(&crossbar->state_available, NULL) != 0) goto fail; crossbar->state_condition_initialized = 1; if (pthread_cond_init(&crossbar->progress, NULL) != 0) goto fail; crossbar->progress_condition_initialized = 1; crossbar->state_ring = calloc(2 * config->num_neurons, sizeof(double)); crossbar->results = calloc(config->num_timesteps * config->num_outputs, sizeof(double)); if (!crossbar->state_ring || !crossbar->results) goto fail; if (convert_weights_to_resistances( reservoir, config->num_neurons, config->num_outputs, config->r_on, config->r_off, &crossbar->resistances, &crossbar->mapping) != 0) goto fail; Crossbar_Config netlist = {.rows = config->num_neurons, .columns = config->num_outputs * 2, .input_series = crossbar->state_ring, .num_samples = config->num_timesteps, .initial_resistance = crossbar->resistances, .model_path = config->model_path, .subcircuit_name = config->subcircuit_name, .load_resistance = config->load_resistance, .time_step = config->time_step, .stop_time = config->num_timesteps * config->time_step, .print_state_nodes = 0}; if (generate_online_crossbar(config->netlist_path, &netlist) != 0) goto fail; *crossbar_out = crossbar; return 0; fail: online_crossbar_destroy(crossbar); return -1; } int online_crossbar_start(Online_Crossbar *crossbar) { if (!crossbar || crossbar->started) return -1; if (ngSpice_Init(callback_text, callback_text, callback_exit, callback_data, callback_init_data, callback_background, crossbar) != 0 || ngSpice_Init_Sync(callback_voltage, NULL, NULL, NULL, crossbar) != 0) return -1; char command[4096]; if (snprintf(command, sizeof(command), "source %s", crossbar->config.netlist_path) >= (int)sizeof(command) || ngSpice_Command(command) != 0) return -1; crossbar->started = 1; ngSpice_SetBkpt(crossbar->config.time_step); if (ngSpice_Command("bg_run") != 0) { crossbar->started = 0; return -1; } return 0; } int online_crossbar_submit(Online_Crossbar *crossbar, size_t timestep, const double *state, double *previous_output, int *output_ready) { if (!crossbar || !state || !output_ready || !crossbar->started || timestep >= crossbar->config.num_timesteps || timestep != crossbar->published_count) return -1; pthread_mutex_lock(&crossbar->mutex); while (!crossbar->failed && timestep >= crossbar->consumed_count + 2) pthread_cond_wait(&crossbar->progress, &crossbar->mutex); if (crossbar->failed) { pthread_mutex_unlock(&crossbar->mutex); return -1; } memcpy(crossbar->state_ring + (timestep % 2) * crossbar->config.num_neurons, state, crossbar->config.num_neurons * sizeof(double)); crossbar->published_count++; pthread_cond_broadcast(&crossbar->state_available); *output_ready = timestep != 0; if (timestep != 0) { if (!previous_output) { pthread_mutex_unlock(&crossbar->mutex); return -1; } while (!crossbar->failed && crossbar->result_count < timestep) pthread_cond_wait(&crossbar->progress, &crossbar->mutex); if (crossbar->failed) { pthread_mutex_unlock(&crossbar->mutex); return -1; } memcpy(previous_output, crossbar->results + (timestep - 1) * crossbar->config.num_outputs, crossbar->config.num_outputs * sizeof(double)); } pthread_mutex_unlock(&crossbar->mutex); return 0; } int online_crossbar_finish(Online_Crossbar *crossbar, double *final_output) { if (!crossbar || !final_output || crossbar->published_count != crossbar->config.num_timesteps) return -1; pthread_mutex_lock(&crossbar->mutex); while (!crossbar->failed && crossbar->result_count < crossbar->config.num_timesteps) pthread_cond_wait(&crossbar->progress, &crossbar->mutex); if (crossbar->failed) { pthread_mutex_unlock(&crossbar->mutex); return -1; } memcpy(final_output, crossbar->results + (crossbar->config.num_timesteps - 1) * crossbar->config.num_outputs, crossbar->config.num_outputs * sizeof(double)); pthread_mutex_unlock(&crossbar->mutex); return 0; } void online_crossbar_destroy(Online_Crossbar *crossbar) { if (!crossbar) return; if (crossbar->started && ngSpice_running()) { ngSpice_Command("bg_halt"); if (crossbar->mutex_initialized) { pthread_mutex_lock(&crossbar->mutex); while (!crossbar->simulation_done && ngSpice_running()) pthread_cond_wait(&crossbar->progress, &crossbar->mutex); pthread_mutex_unlock(&crossbar->mutex); } } if (crossbar->started) ngSpice_Reset(); free(crossbar->resistances); free(crossbar->state_ring); free(crossbar->results); if (crossbar->state_condition_initialized) pthread_cond_destroy(&crossbar->state_available); if (crossbar->progress_condition_initialized) pthread_cond_destroy(&crossbar->progress); if (crossbar->mutex_initialized) pthread_mutex_destroy(&crossbar->mutex); free(crossbar); }