Search code examples
emulationavrsimulatorsimavr

Clock frequency setting doesn't change simulation speed


I'm trying to run the following AVR program on SimAVR:

#include <avr/io.h>
#include <util/delay.h>

int main ()
{
    DDRB |= _BV(DDB5);

    for (;;)
    {
        PORTB ^= _BV(PB5);
        _delay_ms(2000);
    }
}

I've compiled it with F_CPU=16000000. The SimAVR runner is as follows:

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

#include "sim_avr.h"
#include "avr_ioport.h"
#include "sim_elf.h"

avr_t * avr = NULL;

static void* avr_run_thread(void * ignore)
{
    for (;;) {
        avr_run(avr);
    }
    return NULL;
}

void led_changed_hook(struct avr_irq_t* irq, uint32_t value, void* param)
{
    printf("led_changed_hook %d %d\n", irq->irq, value);
}

int main(int argc, char *argv[])
{
    elf_firmware_t f;
    elf_read_firmware("image.elf", &f);
    f.frequency = 16e6;

    const char *mmcu = "atmega328p";
    avr = avr_make_mcu_by_name(mmcu);
    if (!avr) {
        fprintf(stderr, "%s: AVR '%s' not known\n", argv[0], mmcu);
        exit(1);
    }
    avr_init(avr);
    avr_load_firmware(avr, &f);

    avr_irq_register_notify(
        avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 5),
        led_changed_hook,
        NULL);

    pthread_t run;
    pthread_create(&run, NULL, avr_run_thread, NULL);

    for (;;) {}
}

The problem is that I see from the output of led_changed_hook that it runs at ~4x speed. Moreover, changing f.frequency doesn't seem to have any effect on the simulation speed whatsoever.

How do I ensure that SimAVR runs the simulation at the correct real-time speed?


Solution

  • It turns out SimAVR doesn't support timing-accurate simulation of opcodes so the simulation time of running the busy-wait of _delay_ms to completion is completely unrelated to

    • how long it would take on the real MCU
    • the clock frequency of the simulated MCU

    The correct solution is to use a timer interrupt, and then go to sleep on the MCU. The simulator will correctly simulate the timer counters and the sleep will suspend the simulation until the timer fires.

    #include <avr/interrupt.h>
    #include <avr/power.h>
    #include <avr/sleep.h>
    
    int main ()
    {
        DDRB |= _BV(DDB5);
    
        TCCR1A = 0;
        TCCR1B = 0;
        TCNT1  = 0;
        TIMSK1 |= (1 << OCIE1A);
    
        sei();
    
        /* Set TIMER1 to 0.5 Hz */
        TCCR1B |= (1 << WGM12);
        OCR1A   = 31248;
        TCCR1B |= ((1 << CS12) | (1 << CS10));
    
        set_sleep_mode(SLEEP_MODE_IDLE);
        sleep_enable();
        for (;;)
        {
            sleep_mode();
        }
    }
    
    ISR(TIMER1_COMPA_vect){
        PORTB ^= _BV(PB5);
    }