summaryrefslogtreecommitdiff
path: root/src/online_crossbar.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/online_crossbar.c')
-rw-r--r--src/online_crossbar.c413
1 files changed, 413 insertions, 0 deletions
diff --git a/src/online_crossbar.c b/src/online_crossbar.c
new file mode 100644
index 0000000..b01ea19
--- /dev/null
+++ b/src/online_crossbar.c
@@ -0,0 +1,413 @@
+#include "online_crossbar.h"
+
+#include "crossbar_generator.h"
+#include "read_crossbar.h"
+
+#include <ctype.h>
+#include <math.h>
+#include <ngspice/sharedspice.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+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);
+}