Skip to content

Commit

Permalink
hosted: support trigger/adc replay from file
Browse files Browse the repository at this point in the history
  • Loading branch information
via committed Aug 28, 2024
1 parent 4e664bd commit 5ad85a0
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 53 deletions.
33 changes: 22 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,19 +237,30 @@ that will load the binary.
# Simulation
The platform interface is also implemented for a Linux host machine.
```
make PLATFORM=hosted run
make PLATFORM=hosted
```
This will build `viaems` as a Linux executable that will use stdin/stdout as
the console, and event/trigger updates will be sent to stderr . The test
trigger that is enabled by default will provide enough inputs to verify some
basic functionality. Full integration testing using this simulation mode is
planned.

The hosted implementation uses threads to simulate interrupt handling and the
timers/DMA. It attempts to schedule these two threads with realtime priority.
If you experience difficulty keeping sync due to expired triggers (due to
scheduling latency), make sure the threads have permissions to be realtime, or
otherwise constrain it to a single cpu.
the consoled. The executable can take a "replay" file with the -r option that
takes a file used to simulate specific scenarios. Each line in the provided
file carries a type, delay, and extra fields. Currently a trigger and adc
command are implemented. For example, the line
```
t 622 0
```
indicates that, after 622 delay ticks, a trigger input on pin 0 should be
simulated. Likewise, the line
```
a 800 0.0 0.0 1.8 0.8 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
```
indicates that after 800 ticks, the 16 values after (in volts) should be
simulated as an ADC input.

The hosted-mode simulator can be used with flviaems directly to help verify
communications, but it is also used for the integration tests to validate
various scenarios. These tests can be found in the `py/integration-tests`
directory. The tested scenarios produce a VCD dump for all the inputs and
outputs from the simulation, allowing easy visualization with a tool like
GTKWave for debugging purposes .

# Hardware
The current primary hardware platform is an ST Micro STM32F407VGT
Expand Down
10 changes: 8 additions & 2 deletions src/console.c
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,13 @@ static size_t console_feed_line_keys(uint8_t *dest, size_t bsize) {
cbor_encoder_init(&encoder, dest, bsize, 0);

CborEncoder top_encoder;
cbor_encoder_create_map(&encoder, &top_encoder, 2);
cbor_encoder_create_map(&encoder, &top_encoder, 3);
cbor_encode_text_stringz(&top_encoder, "type");
cbor_encode_text_stringz(&top_encoder, "description");

cbor_encode_text_stringz(&top_encoder, "time");
cbor_encode_int(&top_encoder, current_time());

cbor_encode_text_stringz(&top_encoder, "keys");
CborEncoder key_list_encoder;
cbor_encoder_create_array(
Expand All @@ -257,10 +260,13 @@ static size_t console_feed_line(uint8_t *dest, size_t bsize) {
cbor_encoder_init(&encoder, dest, bsize, 0);

CborEncoder top_encoder;
cbor_encoder_create_map(&encoder, &top_encoder, 2);
cbor_encoder_create_map(&encoder, &top_encoder, 3);
cbor_encode_text_stringz(&top_encoder, "type");
cbor_encode_text_stringz(&top_encoder, "feed");

cbor_encode_text_stringz(&top_encoder, "time");
cbor_encode_int(&top_encoder, current_time());

cbor_encode_text_stringz(&top_encoder, "values");
CborEncoder value_list_encoder;
cbor_encoder_create_array(
Expand Down
4 changes: 1 addition & 3 deletions src/platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ void schedule_event_timer(timeval_t);
/* Clear any pending timer */
void clear_event_timer(void);

void platform_init();
void platform_init(int argc, char *argv[]);
/* Benchmark init is minimum necessary to use platform for benchmark */
void platform_benchmark_init(void);

Expand All @@ -53,8 +53,6 @@ size_t console_write(const void *buf, size_t count);
void platform_load_config(void);
void platform_save_config(void);

void platform_enable_event_logging(void);
void platform_disable_event_logging(void);
void platform_reset_into_bootloader(void);

uint32_t platform_adc_samplerate(void);
Expand Down
5 changes: 4 additions & 1 deletion src/platforms/gd32f4/gd32f4_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,10 @@ extern void gd32f4xx_console_init(void);
extern void gd32f4xx_configure_adc(void);
extern void gd32f4xx_configure_pwm(void);

void platform_init() {
void platform_init(int argc, char **argv) {
(void)argc;
(void)argv;

NVIC_SetPriorityGrouping(3); /* 16 priority preemption levels */

gd32f4xx_configure_scheduler();
Expand Down
198 changes: 170 additions & 28 deletions src/platforms/hosted.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,8 @@ static _Atomic timeval_t event_timer_time = 0;
static _Atomic bool event_timer_enabled = false;
static _Atomic bool event_timer_pending = false;

static int event_logging_enabled = 1;
static uint16_t cur_outputs = 0;

void platform_enable_event_logging() {
event_logging_enabled = 1;
}

void platform_disable_event_logging() {
event_logging_enabled = 0;
}

void platform_reset_into_bootloader() {}

timeval_t current_time() {
Expand Down Expand Up @@ -197,16 +188,11 @@ void *platform_interrupt_thread(void *_interrupt_fd) {
exit(1);
}

char output[64];
switch (msg.type) {
case TRIGGER0:
sprintf(output, "# TRIGGER0 %lu\n", (unsigned long)msg.time);
write(STDERR_FILENO, output, strlen(output));
decoder_update_scheduling(0, msg.time);
break;
case TRIGGER1:
sprintf(output, "# TRIGGER1 %lu\n", (unsigned long)msg.time);
write(STDERR_FILENO, output, strlen(output));
decoder_update_scheduling(1, msg.time);
break;
case SCHEDULED_EVENT:
Expand Down Expand Up @@ -366,7 +352,168 @@ static int interrupt_pipes[2];

void platform_benchmark_init() {}

void platform_init() {
struct hosted_args {
const char *read_config_file;
const char *write_config_file;
bool use_realtime;
const char *read_replay_file;
};

static void parse_args(struct hosted_args *args, int argc, char *argv[]) {
*args = (struct hosted_args){ 0 };
int opt;
while ((opt = getopt(argc, argv, "c:o:ri:")) != -1) {
switch (opt) {
case 'r':
args->use_realtime = true;
break;
case 'c':
args->read_config_file = strdup(optarg);
break;
case 'o':
args->write_config_file = strdup(optarg);
break;
case 'i':
args->read_replay_file = strdup(optarg);
break;
default:
fprintf(
stderr,
"usage: viaems [-c config] [-o outconfig] [-r] [-i replayfile]\n");
exit(EXIT_FAILURE);
}
}
}

struct replay_event {
enum {
NO_EVENT,
TRIGGER_EVENT,
ADC_EVENT,
END_EVENT,
} type;

uint32_t trigger;
struct adc_update adc;
};

static FILE *replay_file;
static struct timer_callback replay_timer;
static struct replay_event replay_event = { .type = NO_EVENT };
;

static void replay_callback(void *ptr) {
(void)ptr;

do {
switch (replay_event.type) {
case END_EVENT:
exit(EXIT_SUCCESS);
break;
case TRIGGER_EVENT:
decoder_update_scheduling(replay_event.trigger, replay_timer.time);
break;
case NO_EVENT:
break;
case ADC_EVENT:
sensor_update_adc(&replay_event.adc);
break;
}

/* Fetch next line */
static char *linebuf = NULL;
static size_t linebuf_size = 0;

if (getline(&linebuf, &linebuf_size, replay_file) < 0) {
if (linebuf != NULL) {
free(linebuf);
}
exit(EXIT_SUCCESS);
}

switch (linebuf[0]) {
case 't': {
int delay;
int trigger;
sscanf(linebuf, "t %d %d", &delay, &trigger);

/* Schedule a trigger */
timeval_t next = replay_timer.time + delay;

replay_event.trigger = trigger;
replay_event.type = TRIGGER_EVENT;

replay_timer.data = &replay_event;
schedule_callback(&replay_timer, next);
return;
}
case 'e': {
int delay;
sscanf(linebuf, "e %d", &delay);

/* Schedule an end event */
timeval_t next = replay_timer.time + delay;

replay_event.type = END_EVENT;

replay_timer.data = &replay_event;
schedule_callback(&replay_timer, next);
return;
}
case 'a': {
int delay;

sscanf(linebuf,
"a %d %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f",
&delay,
&replay_event.adc.values[0],
&replay_event.adc.values[1],
&replay_event.adc.values[2],
&replay_event.adc.values[3],
&replay_event.adc.values[4],
&replay_event.adc.values[5],
&replay_event.adc.values[6],
&replay_event.adc.values[7],
&replay_event.adc.values[8],
&replay_event.adc.values[9],
&replay_event.adc.values[10],
&replay_event.adc.values[11],
&replay_event.adc.values[12],
&replay_event.adc.values[13],
&replay_event.adc.values[14],
&replay_event.adc.values[15]);

/* Schedule an end event */
timeval_t next = replay_timer.time + delay;
replay_event.adc.valid = true;
replay_event.adc.time = next;
replay_event.type = ADC_EVENT;

replay_timer.data = &replay_event;
schedule_callback(&replay_timer, next);
return;
}
default:
return;
}
} while (true);
}

static void configure_replay(const char *path) {
replay_file = fopen(path, "r");
replay_timer.callback = replay_callback;
replay_timer.data = NULL;

replay_callback(NULL);
}

void platform_init(int argc, char *argv[]) {
struct hosted_args args;
parse_args(&args, argc, argv);

if (args.read_replay_file) {
configure_replay(args.read_replay_file);
}

/* Initalize mutexes */
pthread_mutexattr_t im_attr;
Expand All @@ -387,44 +534,39 @@ void platform_init() {
pthread_t timebase;
pthread_attr_t timebase_attr;
pthread_attr_init(&timebase_attr);

pthread_attr_setinheritsched(&timebase_attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&timebase_attr, SCHED_RR);
struct sched_param timebase_param = {
.sched_priority = 1,
};
pthread_attr_setschedparam(&timebase_attr, &timebase_param);

if (pthread_create(&timebase,
&timebase_attr,
args.use_realtime ? &timebase_attr : NULL,
platform_timebase_thread,
&interrupt_pipes[1])) {
perror("pthread_create");
/* Try without realtime sched */
if (pthread_create(
&timebase, NULL, platform_timebase_thread, &interrupt_pipes[1])) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
exit(EXIT_FAILURE);
}

pthread_t interrupts;
pthread_attr_t interrupts_attr;
pthread_attr_init(&interrupts_attr);

pthread_attr_setinheritsched(&interrupts_attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&interrupts_attr, SCHED_RR);
struct sched_param interrupts_param = {
.sched_priority = 2,
};
pthread_attr_setschedparam(&interrupts_attr, &interrupts_param);

if (pthread_create(&interrupts,
&interrupts_attr,
args.use_realtime ? &interrupts_attr : NULL,
platform_interrupt_thread,
&interrupt_pipes[0])) {
perror("pthread_create");
/* Try without realtime sched */
if (pthread_create(
&interrupts, NULL, platform_interrupt_thread, &interrupt_pipes[0])) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
exit(EXIT_FAILURE);
}
}
4 changes: 3 additions & 1 deletion src/platforms/stm32f4-discovery.c
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,9 @@ void platform_benchmark_init() {
platform_init_usb();
}

void platform_init() {
void platform_init(int argc, char *argv[]) {
(void)argc;
(void)argv;

/* 168 Mhz clock */
rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_168MHZ]);
Expand Down
9 changes: 4 additions & 5 deletions src/platforms/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ static int int_disables = 0;
static int output_states[16] = { 0 };
static int gpio_states[16] = { 0 };

void platform_enable_event_logging() {}

void platform_disable_event_logging() {}

void platform_reset_into_bootloader() {}

void set_pwm(int pin, float val) {
Expand Down Expand Up @@ -136,7 +132,10 @@ size_t console_write(const void *ptr, size_t max) {
}
void platform_benchmark_init() {}

void platform_init() {
void platform_init(int argc, char *argv[]) {
(void)argc;
(void)argv;

Suite *viaems_suite = suite_create("ViaEMS");

suite_add_tcase(viaems_suite, setup_util_tests());
Expand Down
Loading

0 comments on commit 5ad85a0

Please sign in to comment.