# PDI-2025: Practice 3-A: Basic Time Management The objective of this practice is that the student learns to design, implement and use a set of basic timing services. ### Schedulle This practice is planned to be carried out in 1 week. ### P3 project creation In order to create the ```P3-A``` project follow the same steps as for creating ```P2```. ## CLINT Timer Handling ### Installing an CLINC timer handler See the functions ```install_local_timer_handler``` and ```uninstall_local_timer_handler``` in file ```dispatch.h```. They have the following prototypes: ```c= void install_local_timer_handler( void (*f)(void) ); void uninstall_local_timer_handler(); ``` The first function receives a pointer to a timer handler (ISR). When ```MTIME``` register reaches ```MTIMECMP``` value, ISR is automatically called by the system. The parameter ```void (*f)(void)``` type means that ISRs are functions that receive NO parameters NOR return values. They use global variables to communicate with ```main```. ### Define CLINT time compare gap See the function ```local_timer_set_gap``` in file ```dispatch.h```: ```c= void local_timer_set_gap( uint64_t gap ); ``` They are used to define the increment applied to ```MTIMECMP``` register. ### Enable CLINC timer interrupts See the functions ```enable_timer_clinc_irq``` and ```disable_timer_clinc_irq``` in file ```dispatch.h```: ```c= // SET MIE REG TIMER BIT (Timer irq enable) void enable_timer_clinc_irq(void); // CLEAR MIE REG TIMER BIT (Timer irq disable) void disable_timer_clinc_irq(void); ``` They are used to enable/disable CLINC timer IRQs. ### Exercise code In ```main.c``` file and before the ```main``` function add the following ISR handler and variables: ```c= uint32_t ticks = 0; void timer_handler(void) { printf("%d ", ticks ); ticks++; } ``` Write to ```main``` function the following code: ```c= int main() { printf("----------TIMER IRQ---------\n"); install_local_timer_handler( timer_handler ); local_timer_set_gap( 10000000 ); // CLINT timer clock is 10Mhz enable_timer_clinc_irq(); enable_irq(); // Global interrupt enable while( ticks <= 10 ) ; // empty statement printf("\n----------END---------\n"); } ``` ### Timer IRQ test In order to test the program follow the steps below: * Build P3-A project and correct any typing errors. Run it typing ```./runP3-A``` in eclipse terminal window. The program must print the value of ```ticks``` variable each second. After 10 seconds, the output should look like as follows: ``` atcsol@atcsol:~/eclipse-workspace/P3-A$ ./runP3-A TCP Sockets... Connected to picsimlab-PDI... ----------TIMER IRQ--------- 0 1 2 3 4 5 6 7 8 9 10 ----------END--------- atcsol@vmatc:~/eclipse-workspace/P3-A$ ``` :::success :raising_hand: Describe the execution flow of the code. :raising_hand: Pay attention to all IRQ related configuration issues you must take in account in order to handle the timer IRQ properly. ::: ## Student work Modify the given example as follows: 1. Select a timer gap in order to run timer ISR handler 10 times per second. 2. The program must print two messages, one every second and the other every 5 seconds. 3. Both messages must be printed in ```main``` function, following a top/bottom architecture. Timer ISR just count ticks and signals timeout by setting a boolean flag variable. :::info :information_source: Do not forget to declare all the variables you may need. Also include all the headers files that may be required, ::: :::success :raising_hand: Describe the execution flow of the code. :raising_hand: Do the messages always appear in the same order? For example, what is happening if we get an output like the following? Is it right? ::: ``` Msg 1 Msg 1 Msg 1 Msg 1 Msg 1 Msg 2 Msg 1 Msg 1 Msg 1 Msg 1 Msg 2 Msg 1 ``` :::spoiler ```c= #include "riscv_types.h" #include "dispatch.h" #include "clinc.h" #include "log.h" uint32_t cnt_1=10, cnt_2=50; uint32_t flag_1=0, flag_2=0; void timer_handler(void) { cnt_1--; cnt_2--; if (cnt_1 == 0) { cnt_1 = 10; flag_1 = 1; } if (cnt_2 == 0) { cnt_2 = 50; flag_2 = 1; } } int main() { printf("----------TIMER IRQ---------\n"); install_local_timer_handler( timer_handler ); local_timer_set_gap( 1000000 ); // CLINT timer clock is 10Mhz enable_timer_clinc_irq(); enable_irq(); // Global interrupt enable while(1) { if ( flag_1 ) { printf("---> Msg 1\n"); flag_1 = 0; } if ( flag_2 ) { printf(" ---> Msg 2\n"); flag_2 = 0; } } } ``` ::: <!-- ## Student work 2 Implementation of an GPTIMER basic driver. --> --- ## Monotonic time service One of the most basic timing services that can be implemented by the real time counter is a universal time manager via a monotonic clock. A monotonic clock is one that is incremented periodically during the execution of a system. The value taken by the clock can thus be associated to the universal time by means of a reference. For this practice we will use the following routines (***already coded***) that facilitate the implementation of a monotonic clock over a universal time encoding known as Y2K. This encoding uses a 32-bit integer to set the seconds that have passed since 1 January 2000. :::info :information_source: A routine that initialises the variables that manage the monotonic clock and sets a reference value in Y2K format as the initial universal time. Its prototype is as follows: ```c= void InitMonotonicClock(uint32_t Y2KTimeRef); ``` :information_source: Function that returns the current value of the universal time encoded in Y2K. The prototype of the function is as follows: ```c= void uint32_t GetUniversalTime_Y2K (); ``` :information_source: A function that prints the value of the universal time encoded in Y2K that is passed via the seconds_from_y2K parameter. The prototype of the function is as follows: ```c= void print_date_time_from_Y2K(uint32_t seconds_from_y2K); ``` :information_source: Function that encodes in Y2K a date and time passed by parameter using the fields day, month, year, hour, minutes and seconds. The prototype of the function is as follows: ```c= uint32_t date_time_to_Y2K( uint8_t day, uint8_t month, uint8_t year, uint8_t hour, uint8_t minutes, uint8_t seconds); ``` :information_source: Finally and defined as a macro you can find ```get_ticks_from_reset()```. This macro returns the value of the real time counter, which contains the count of ticks since system boot. Remenber that the clock is 10Mhz : ```c= #define get_ticks_from_reset() ((volatile uint64_t)(*p_clinc_mtime)) ``` ::: Download the files ```riscv_monotonic_clock.c``` and ```riscv_monotonic_clock.h``` from: * [riscv_monotonic_clock.c](https://drive.google.com/file/d/1dCBnusMVLgFOFeD6059Z9cU7q3ELNbVp/view?usp=sharing) * [riscv_monotonic_clock.h](https://drive.google.com/file/d/1tmXETLX-fOoO03SuZFrC5v_ZGU3tgPgI/view?usp=sharing) and add them to the project. Write to ```main``` function the following code: ```c= #include "riscv_types.h" #include "dispatch.h" #include "log.h" #include "clinc.h" #include "riscv_monotonic_clock.h" int main() { printf("----------MONOTONIC TIME---------\n"); // Time set: 12/10/2023 12:40 InitMonotonicClock(date_time_to_Y2K(12, 10, 24, 12, 40, 0 )); uint32_t previous = 0; uint32_t current = 0; do { current = GetUniversalTime_Y2K(); if ( current != previous ) { print_date_time_from_Y2K( current ); previous = current; } } while ( 1 ); printf("\n----------END---------\n"); } ``` :::info :information_source: Do not forget to declare all the variables you may need. Also include all the headers files that may be required ::: :::warning :warning: In order to build the project it is necessary to modifiy the project settings and include a library as follows. Open project settings, select ```libraries``` in ```Cross GCC Linker``` section and add ```gcc``` to the libraries window. ![Screenshot from 2024-11-05 19-32-36](https://hackmd.io/_uploads/BySoSJuZ1x.png) ::: Build and run the code. The output will show a sequence of timed messages every second. ``` ----------MONOTONIC TIME--------- Fecha: 12 | 10 | 2024 Hora: 12:40:0 Fecha: 12 | 10 | 2024 Hora: 12:40:1 Fecha: 12 | 10 | 2024 Hora: 12:40:2 Fecha: 12 | 10 | 2024 Hora: 12:40:3 Fecha: 12 | 10 | 2024 Hora: 12:40:4 Fecha: 12 | 10 | 2024 Hora: 12:40:5 Fecha: 12 | 10 | 2024 Hora: 12:40:6 Fecha: 12 | 10 | 2024 Hora: 12:40:7 Fecha: 12 | 10 | 2024 Hora: 12:40:8 Fecha: 12 | 10 | 2024 Hora: 12:40:9 ``` ## Student work ### Coding delay and wait_until services Given the following function description in ```riscv_monotonic_clock.h```, complete the code of ```delay``` and ```wait_until``` functions in ```riscv_monotonic_clock.c``` ```c= void delay( uint64_t ticks_to_wait ) { // To complete // Wait in an active loop for the number of MTIME ticks // indicated by the ticks parameter // Use get_ticks_from_reset() macro to get current value of MTIME } void wait_until( uint64_t target_ticks ) { // To complete // Wait in an active loop until the current MTIME ticks reaches // the value indicated by the target_ticks parameter. // Use get_ticks_from_reset() macro to get current value of MTIME } ``` In order to better undestand and clarify the use of Delay and WaitUntil primitives, please take a look to: [Delay and WaitUntil](https://hackmd.io/@SistemasEmpotrados/SE-DELAY-WAITUNITL) :::spoiler ```c= void delay( uint64_t ticks_to_wait ) { uint64_t target_ticks = get_ticks_from_reset() + ticks_to_wait; uint64_t current_ticks; do { current_ticks = get_ticks_from_reset(); } while ( current_ticks < target_ticks ); } void wait_until( uint64_t target_ticks ) { uint64_t current_ticks; do { current_ticks = get_ticks_from_reset(); } while ( current_ticks < target_ticks ); } ``` ::: ### Modify previous ```main``` function Modify previous ```main``` function in order to print the universal time each second. Implement two versions: one using ```delay``` and other using ```wait_until```. ```c= #include "riscv_types.h" #include "dispatch.h" #include "log.h" #include "clinc.h" #include "riscv_monotonic_clock.h" int main() { printf("----------MONOTONIC TIME---------\n"); // Time set: 12/10/2023 12:40 InitMonotonicClock(date_time_to_Y2K(12, 10, 23, 12, 40, 0 )); uint32_t current = 0; // Print universal time time each second do { // Modify if necessary current = GetUniversalTime_Y2K(); print_date_time_from_Y2K( current ); // Modify if necessary } while ( 1 ); printf("\n----------END---------\n"); } ``` :::info :information_source: Do not forget to declare all the variables you may need. ::: ## Second Week The proposed work about Cyclic Executives for the second week can be found [here](https://hackmd.io/@dasilvin/PDI-P3-B). <!-- ### OPTIONAL: Implement an OneShot/Cyclic callback service using CLINT timer interrupt The objective of this section is to implement a service that allows to schedule the execution of a routine with a precision of milliseconds. The scheduled function must print the elapsed real time ticks in order to verify if it correspond to the time requested. The service will run as follows: ```c= #define ONESHOT 1 #define CYCLIC 0 uint64_t prev_rt_cnt = 0; // previous real time counter uint64_t curr_rt_cnt = 0; // current real time counter typedef struct { void (*user_callback_function)(); uint8_t is_oneshot; uint32_t timer_value; volatile uint32_t timer_counter; } timer_t; timer_t timer1; void deferred_user_processing(void) { // Print elapsed real time counter value // TO COMPLETE } void timer_service(void) { // TO COMPLETE // Decrement timer counter // When timer counter reaches zero call user callback function // If timer is cyclic reload timer counter with timer value } void program_timed_function( void (*callback), // User function to callback when time expires uint32_t mseconds, // Time in milliseconds uint8_t oneshot_periodic ) // 1: one shot, 0: cyclic { // Save values in timer1 timer1.user_callback_function = callback; timer1.is_oneshot = oneshot_periodic; timer1.timer_value = mseconds; timer1.timer_counter = mseconds; } int main() { printf("----------ONE SHOT/CYCLIC CALLBACK---------\n"); install_local_timer_handler( timer_service ); local_timer_set_gap( 10000 ); // miliseconds timer enable_timer_clinc_irq(); enable_irq(); // Global interrupt enable // One shot test // program_timed_function( deferred_user_processing, 200 /* mseconds*/ , ONESHOT); // oneshot, 200 ms // Cyclic test program_timed_function( deferred_user_processing, 500 /* mseconds*/ , CYCLIC); // cylic, 500 ms prev_rt_cnt = get_ticks_from_reset(); // Get current real time counter value delay( CLINT_CLOCK * 5 ); // wait 5 seconds and ends printf("\n----------END---------\n"); } ``` For the ***Cyclic*** test, the output values should approximate those shown below. For ***Oneshot*** test the elapset ticks will be printed just once. ``` -----------ONE SHOT/CYCLIC CALLBACK--------- TIME ELAPSED: 00000000004C4B18 TIME ELAPSED: 00000000004C4BF4 TIME ELAPSED: 00000000004C4AE6 TIME ELAPSED: 00000000004C4B9A TIME ELAPSED: 00000000004CA5F4 ``` :::info :information_source: Do not forget to declare all the variables and auxiliary functions you may need. Also include all the headers files that may be required. ::: :::success :raising_hand: How long does 0x4C4B18 real time count define? :raising_hand: What should the real time count be for one tenth of a second? ::: -->