metalsvm/arch/x86/scc/RCCE_power_management.c
Stefan Lankes c738a64d57 integration of RCCE in MetalSVM (untested version)
Attention: currently, MetalSVM didn't support the floating point unit

=> no using of RCCE_wtime
=> no using of the data type RCCE_double
=> RCCE_init expect an integer value as frequency in MHZ
2011-03-24 11:21:38 +01:00

619 lines
22 KiB
C

//***************************************************************************************
// Power management (voltage + frequency) routines.
//***************************************************************************************
//
// Author: Rob F. Van der Wijngaart
// Intel Corporation
// Date: 12/22/2010
//
//***************************************************************************************
//#define POWER_DEBUG 1
//
// Copyright 2010 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include <asm/RCCE_lib.h>
#include <asm/SCC_API.h>
#include <asm/RCCE_lib_pwr.h>
#ifdef CONFIG_ROCKCREEK
//......................................................................................
// GLOBAL VARIABLES USED BY THE LIBRARY
//......................................................................................
t_vintp frequency_change_virtual_address[RCCE_MAXNP/2]; // one frequency change register per
// tile, RCCE_MAXNP/2 tiles on chip
t_vintp RPC_virtual_address; // only one RPC on chip
// keep track of frequency domain masters inside voltage domain
int RCCE_ue_F_masters[4];
RCCE_COMM RCCE_V_COMM;
RCCE_COMM RCCE_F_COMM;
RCCE_COMM RCCE_P_COMM;
#ifndef SCC
// the following structure is maintained in the MPB of one core, although space for it
// needs to be claimed on all cores. Interpretation of queue array elements:
// 0: no core owns this slot
// (m+1): core m owns this slot
// -(m+1): core m owns this slot, and is the head of the queue
// The queue is maintained as a circular buffer with a contiguous (in mod space) set
// of non-zeroes, representing the RPC priority queue
RCCE_RPC_REGULATOR *RCCE_RPC_regulator;
#endif
// the following array contains triples of voltage/VID value/max_frequency
triple RC_V_MHz_cap[] = {
/* 0 */ {0.7, 0x70, 460},
/* 1 */ {0.8, 0x80, 598},
/* 2 */ {0.9, 0x90, 644},
/* 3 */ {1.0, 0xA0, 748},
/* 4 */ {1.1, 0xB0, 875},
/* 5 */ {1.2, 0xC0, 1024},
/* 6 */ {1.3, 0xD0, 1198}
};
int RCCE_set_power_active = 0;
int RC_current_voltage_level = RC_DEFAULT_VOLTAGE_LEVEL;
int RC_current_frequency_divider = RC_DEFAULT_FREQUENCY_DIVIDER;
#ifndef SCC
long long RC_time_at_birth;
#endif
// tile clock change words, assuming constant router ratio of 2
unsigned int RC_frequency_change_words[][2] = {
// rtr clock ratio, bits 25:8
/* NOP */ { 2, 0x00000000},
/* 1 */ { 2, 0x000038e0},
/* 2 */ { 2, 0x000070e1},
/* 3 */ { 2, 0x0000a8e2},
/* 4 */ { 2, 0x0000e0e3},
/* 5 */ { 2, 0x000118e4},
/* 6 */ { 2, 0x000150e5},
/* 7 */ { 2, 0x000188e6},
/* 8 */ { 2, 0x0001c0e7},
/* 9 */ { 2, 0x0001f8e8},
/* 10 */ { 2, 0x000230e9},
/* 11 */ { 2, 0x000268ea},
/* 12 */ { 2, 0x0002a0eb},
/* 13 */ { 2, 0x0002d8ec},
/* 14 */ { 2, 0x000310ed},
/* 15 */ { 2, 0x000348ee},
/* 16 */ { 2, 0x000380ef}
};
// RCCE_FDOM_masters[VDOM][i] = physical core ID of frequency domain master
// within RCCE power domain VDOM (logical power domain)
int RCCE_FDOM_masters[6][4] = {
{ 0, 2, 12, 14}, { 4, 6, 16, 18}, { 8, 10, 20, 22},
{24, 26, 36, 38}, {28, 30, 40, 42}, {32, 34, 44, 46}
};
//......................................................................................
// END GLOBAL VARIABLES USED BY THE LIBRARY
//......................................................................................
#ifndef SCC
// fake MallocConfigReg; just return the physical address that would be accessed on RCK
int *MallocConfigReg(unsigned int address) { return (int *)address;}
#endif
static int RCCE_VDOM(int coreID) {
// back out true tile coordinates, then divide by 2 in both directions (vdoms are 2x2
// tiles each) and linearize to determine the logical voltage domain. That is not the
// same as the physical voltage domain
int x, y;
x = X_PID(coreID);
y = Y_PID(coreID);
return ((x/2)+(y/2)*3);
}
static int RCCE_FDOM(int coreID) {
// back out true tile coordinates, then linearize to detemine the logical frequency
// domain, which equals the logical tile number. That is not the same as the
// physical tile number
int x, y;
x = X_PID(coreID);
y = Y_PID(coreID);
return(6*y+x);
}
static int RCCE_voltage_domain(int ue) {
return(RCCE_VDOM(RC_COREID[ue]));
}
int RCCE_power_domain() {
return(RCCE_voltage_domain(RCCE_IAM));
}
static int RCCE_frequency_domain(int ue) {
return(RCCE_FDOM(RC_COREID[ue]));
}
static int RCCE_voltage_domain_master() {
return(RCCE_V_COMM.member[0]);
}
int RCCE_power_domain_master() {
return(RCCE_voltage_domain_master());
}
int RCCE_power_domain_size() {
return(RCCE_V_COMM.size);
}
#ifndef SCC
////////////////////////////////////////////////////////////////////////////////////////
// SUPPORT FUNCTIONS TO MAINTAIN RPC QUEUE
////////////////////////////////////////////////////////////////////////////////////////
static int RCCE_RPC_add_self_to_queue() {
int ue, ueq, error, found_slot;
t_vcharp RPC_buffer[REGULATOR_LENGTH];
RCCE_RPC_REGULATOR *privp;
volatile int *queue;
// privp needs to point to space to hold the RPC regulator that is cache lined padded
privp = (RCCE_RPC_REGULATOR *) RPC_buffer;
queue = privp->queue;
error = RCCE_SUCCESS;
if (error=RCCE_get((t_vcharp)privp, (t_vcharp)RCCE_RPC_regulator, REGULATOR_LENGTH, RPC_ROOT))
{
#ifdef POWER_DEBUG
printf("UE %d could not copy regulator from MPB\n", RCCE_IAM); fflush(NULL);
#endif POWER_DEBUG
return(error);
}
else {
#ifdef POWER_DEBUG
printf("UE %d read regulator %d, %d, %d, %d, %d, %d, clock %lld\n", RCCE_IAM,
queue[0], queue[1], queue[2], queue[3], queue[4], queue[5],
privp->start_time); fflush(NULL);
#endif
}
// find the head of the queue
int head = -1;
for (ue = 0; ue<RC_NUM_VOLTAGE_DOMAINS; ue++) {
if (head==-1 && queue[ue]<0) {
head = ue;
break;
}
}
// if there was no head, the queue was empty; insert calling ue in first slot
if (head==-1) {
queue[0] = -(RCCE_IAM+1);
}
else {
// there was a head, so we add calling ue to end of queue
for (found_slot=0,ue = 1; ue<RC_NUM_VOLTAGE_DOMAINS; ue++) {
ueq = (head+ue)%(RC_NUM_VOLTAGE_DOMAINS);
if (queue[ueq]==0) {
queue[ueq] = RCCE_IAM+1;
found_slot = 1;
break;
}
}
if (!found_slot) {
error = RCCE_ERROR_RPC_INTERNAL;
}
}
error=RCCE_put((t_vcharp)RCCE_RPC_regulator, (t_vcharp)privp, REGULATOR_LENGTH, RPC_ROOT);
return(error);
}
static long long RCCE_RPC_remove_self_from_queue_and_reset_start(int *error) {
int ue;
t_vcharp RPC_buffer[REGULATOR_LENGTH];
RCCE_RPC_REGULATOR *privp;
volatile int *queue;
// privp needs to point to space to hold the RPC regulator that is cache lined padded
privp = (RCCE_RPC_REGULATOR *) RPC_buffer;
queue = privp->queue;
*error = RCCE_SUCCESS;
RCCE_get((t_vcharp)privp, (t_vcharp)RCCE_RPC_regulator, REGULATOR_LENGTH, RPC_ROOT);
// find the head of the queue
int head = -1;
for (ue = 0; ue<RC_NUM_VOLTAGE_DOMAINS; ue++) if (head==-1 && queue[ue]<0) {
head = ue;
break;
}
// if there was no head, or the head does not match the calling ue, emit error
if (head==-1 || queue[head] != -(RCCE_IAM+1)) {
*error = RCCE_ERROR_RPC_INTERNAL;
return(-1);
}
else {
// found the head of the head of the queue; remove, mark the next queue item as
// the head, if present, and reset timer
queue[head] = 0;
if (queue[(head+1)%(RC_NUM_VOLTAGE_DOMAINS)]>0) {
queue[(head+1)%(RC_NUM_VOLTAGE_DOMAINS)] *= -1;
}
privp->start_time = RC_global_clock();
}
RCCE_put((t_vcharp)RCCE_RPC_regulator, (t_vcharp)privp, REGULATOR_LENGTH, RPC_ROOT);
// return start time of request just issued, to be used in the RCCE_wait_power() call
return(privp->start_time);
}
static int RCCE_RPC_my_turn() {
int ue;
t_vcharp RPC_buffer[REGULATOR_LENGTH];
RCCE_RPC_REGULATOR *privp;
volatile int *queue;
// privp needs to point to space to hold the RPC regulator that is cache lined padded
privp = (RCCE_RPC_REGULATOR *) RPC_buffer;
queue = privp->queue;
RCCE_get((t_vcharp)privp, (t_vcharp)RCCE_RPC_regulator, REGULATOR_LENGTH, RPC_ROOT);
// find the head of the queue
int head = -1;
for (ue = 0; ue<(RC_NUM_VOLTAGE_DOMAINS); ue++) {
if (head==-1 && queue[ue]<0) {
head = ue;
break;
}
}
// if there was no head, or the head does not match the calling ue, or time has not
// yet come, return failure
if (head==-1 || queue[head] != -(RCCE_IAM+1) ||
RC_global_clock() < privp->start_time + RC_WAIT_CYCLES) return(0);
else return(1);
}
////////////////////////////////////////////////////////////////////////////////////////
// END SUPPORT FUNCTIONS TO MAINTAIN RPC QUEUE
////////////////////////////////////////////////////////////////////////////////////////
#endif
// when setting new voltage, model competition for RPC by maintaining short queue of RPC
// service requests, and by blocking the calling core if the queue is not empty
static long long RC_set_voltage(int domain, int Vlevel, int *error){
unsigned int vidwd;
long long start_time = 0;
int ready_confirmation;
//we only maintain the RPC queue on the simulator, and use the SCC auto-block feature
//for multiple outstanding RPC requests to make sure the target voltage is reached
#ifndef SCC
// because there can only be one power change request in flight at any time for each
// core, we know we can blindly add the calling core to the RPC queue; the queue
// update must be atomic, so we protect it with a lock */
RCCE_acquire_lock(RPC_ROOT);
*error = RCCE_RPC_add_self_to_queue();
RCCE_release_lock(RPC_ROOT);
// block until we have come to the front of the queue AND can start having our RPC
// request serviced; we keep checking whether we can remove ourself from the queue
// without acquiring the RPC lock, for efficiency. When it looks like we're ready,
// we acquire the lock and check again. If we mistakenly thought that our turn had
// come because we checked the queue in the middle of an update by another core, we
// can now confirm whether we are truly ready to have our RPC request processed.
ready_confirmation = 0;
while (!ready_confirmation) {
while (!RCCE_RPC_my_turn());
RCCE_acquire_lock(RPC_ROOT);
if (ready_confirmation=RCCE_RPC_my_turn()) {
start_time = RCCE_RPC_remove_self_from_queue_and_reset_start(error);
}
RCCE_release_lock(RPC_ROOT);
}
#else
// FILE *power_file;
#ifdef POWER_DEBUG
printf("UE %d trying to write VID word %x (level %d, V %lf) to address %x\n",
RCCE_IAM, VID_word(Vlevel, domain), Vlevel, RC_V_MHz_cap[Vlevel].volt,
RPC_virtual_address); fflush(NULL);
#endif
*RPC_virtual_address = VID_word(Vlevel, domain);
*error = RCCE_SUCCESS;
// char name[50] = "/shared/DEMOS/ECO_Q/power";
// char postfix[50];
// sprintf(postfix, "%d", RCCE_power_domain());
// strcat(name, postfix);
// printf("Core %d opens file %s and writes %d\n", RCCE_IAM, name, step);
// power_file = fopen(name,"w");
// fprintf(power_file, "%d", step);
// fclose(power_file);
#endif
return(start_time);
}
// return for a given voltage domain the integral value that needs to be written
// to the RPC to establish a new voltage. Bit 16 needs to be one. Shiftid is the
// corrected value to be used to identify the physuical voltage domain. Domains
// 2 and 6 are skipped, these are used for non-tile domains; the domain occupies
// bits 8:10 in the control value
int VID_control_value(int voltage_domain) {
int VID_shift[] = {4,5,7,0,1,3};
int shiftid = VID_shift[voltage_domain];
return((1<<16) + (shiftid<<8));
}
// generate the complete word to be written to the RPC
unsigned int VID_word(int Vlevel, int power_domain) {
unsigned int cv, vid;
cv = VID_control_value(power_domain);
vid = RC_V_MHz_cap[Vlevel].VID;
return(cv+vid);
}
// voltage level corresponding to the chosen frequency
int RC_voltage_level(int Fdiv) {
int Vlevel, found, MHz;
MHz = RC_GLOBAL_CLOCK_MHZ/Fdiv;
found = 0;
for (Vlevel=0; Vlevel<=RC_NUM_VOLTAGE_LEVELS; Vlevel++)
if (RC_V_MHz_cap[Vlevel].MHz_cap >= MHz) {found=1; break;}
if (found) return(Vlevel);
else return(-1);
}
// generate the complete word to be written to the CRB for tile clock freq
unsigned int FID_word(int Fdiv, int tile_ID) {
#ifdef SCC
unsigned int lower_bits = (*(frequency_change_virtual_address[tile_ID]))&((1<<8)-1);
#else
unsigned int lower_bits = 0x0;
#endif
return(((RC_frequency_change_words[Fdiv][1])<<8)+lower_bits);
}
// get the complete word from the CRB for tile clock frequency
int get_divider(int tile_ID) {
#ifdef SCC
int step;
unsigned int word;
// shift to the right to get the correct bits
word = (*(frequency_change_virtual_address[tile_ID]))>>8;
for (step=0; step<=16; step++) {
if (word==RC_frequency_change_words[step][1]) break;
}
if (word==RC_frequency_change_words[step][1]) return(step);
else return(-1);
#else
return(0);
#endif
}
// print all tile clock dividers
void print_dividers(void) {
int tile_ID;
for (tile_ID=0; tile_ID<24; tile_ID++)
printf("Clock divider for tile %d is %d\n", tile_ID, get_divider(tile_ID));
}
#ifndef SCC
long long RC_global_clock(void){
return(_rdtsc()-RC_time_at_birth);
}
#endif
// use these color functions for the power domain communicators
int RCCE_V_color(int rank, void *nothing) {return(RCCE_VDOM(RC_COREID[rank]));}
int RCCE_F_color(int rank, void *nothing) {return(RCCE_FDOM(RC_COREID[rank]));}
int RCCE_init_RPC(int *RC_COREID, int RCCE_IAM, int RCCE_NP) {
int ue, tile, i, x, y;
void *nothing = NULL;
// set the frequency domain masters of power domain containing the calling core
for (tile=0; tile<4; tile++)
RCCE_ue_F_masters[tile] = RCCE_FDOM_masters[RCCE_VDOM(RC_COREID[RCCE_IAM])][tile];
// create communicators that span the local voltage (8 cores) and local frequency
// domains, respectively
RCCE_comm_split(RCCE_V_color, nothing, &RCCE_V_COMM);
RCCE_comm_split(RCCE_F_color, nothing, &RCCE_F_COMM);
// copy the voltage domain communicator to the externally visible power communicator
RCCE_P_COMM = RCCE_V_COMM;
// compute virtual addresses of RPC register and tile clock dividers
RPC_virtual_address = (t_vintp) MallocConfigReg(RPC_PHYSICAL_ADDRESS);
for (tile=0; tile<RCCE_MAXNP/2; tile++) {
x = tile%6; y = tile/6;
frequency_change_virtual_address[tile] =
(t_vintp) MallocConfigReg(CRB_ADDR(x,y)+TILEDIVIDER);
}
#ifndef SCC
// space for RPC regulator has been preallocated; while all UEs use the regulator
// in RPC_ROOT's MPB, they need only compute the offset in their own MPB
RCCE_RPC_regulator = (RCCE_RPC_REGULATOR *)(RCCE_comm_buffer[RCCE_IAM]-REGULATOR_LENGTH);
// we do not need to initialize the regulator, zeroes are fine
// create semblance of global time (i.e. across different cores): place barrier that
// roughly synchronizes all cores and store value of local clock as offset
RCCE_barrier(&RCCE_COMM_WORLD);
RC_time_at_birth = _rdtsc();
#endif
printf("UE %d, Core ID %d; size of V dom %d is %d, size of F dom %d is %d\n",
RCCE_IAM, RC_MY_COREID, RCCE_VDOM(RC_COREID[RCCE_IAM]),
RCCE_V_COMM.size, RCCE_FDOM(RC_COREID[RCCE_IAM]), RCCE_F_COMM.size);
fflush(NULL);
return(RCCE_SUCCESS);
}
// this function takes as an input a requested new frequency divider for the
// globally distributed clock, as applied to all the tiles in the voltage
// domain containing the calling core. It automatically selects the right
// minimum voltage so support the new target frequency. The return value is
// the actual frequency divider applied.
int RCCE_iset_power(int Fdiv, RCCE_REQUEST *req, int *new_Fdiv, int *new_Vlevel) {
// Fdiv: requested clock divider
int error, ue, Vlevel;
// only the domain master executes the actual request
if (!(RCCE_IAM == RCCE_voltage_domain_master())) return(RCCE_SUCCESS);
// Cannot have more than one power stepping in flight
if (RCCE_set_power_active)
return(RCCE_error_return(RCCE_debug_RPC, RCCE_ERROR_MULTIPLE_RPC_REQUESTS));
if (Fdiv > RC_MAX_FREQUENCY_DIVIDER) Fdiv = RC_MAX_FREQUENCY_DIVIDER;
else
if (Fdiv < RC_MIN_FREQUENCY_DIVIDER) Fdiv = RC_MIN_FREQUENCY_DIVIDER;
*new_Fdiv = Fdiv;
// determine the voltage level for the requested frequency divider
Vlevel = RC_voltage_level(Fdiv);
*new_Vlevel = Vlevel;
// if the voltage could not be adjusted to accommodate the requested frequency
// divider, exit the function
if (Vlevel <0) return(RCCE_error_return(RCCE_debug_RPC, RCCE_ERROR_FDIVIDER));
req->release = 0;
req->old_voltage_level = RC_current_voltage_level;
req->new_voltage_level = Vlevel;
req->old_frequency_divider = RC_current_frequency_divider;
req->new_frequency_divider = Fdiv;
// if new frequency divider greater than current, adjust frequency immediately;
// this can always be done safely if the current power state is feasible
if (req->new_frequency_divider >= req->old_frequency_divider){
// need to set frequency divider on all tiles of the voltage domain
RCCE_set_frequency_divider(Fdiv, new_Fdiv);
RC_current_frequency_divider = req->new_frequency_divider;
}
RCCE_set_power_active = 1;
req->start_cycle = RC_set_voltage(RCCE_voltage_domain(RCCE_IAM),
Vlevel, &error);
return(RCCE_error_return(RCCE_debug_RPC, error));
}
// set frequency divider on a single tile
static int RC_set_frequency_divider(int tile_ID, int Fdiv) {
// we don't have to do anything for frequency in the emulator
// printf("UE %d writes FID_word %x to address %x on tile %d\n", RCCE_IAM,
// (int *)(FID_word(Fdiv, tile_ID)), frequency_change_virtual_address[tile_ID],
// tile_ID);
#ifdef SCC
*(frequency_change_virtual_address[tile_ID]) = FID_word(Fdiv, tile_ID);
#endif
return(RCCE_SUCCESS);
}
int RCCE_wait_power(RCCE_REQUEST *req) {
int new_Fdiv, ue, Fdiv;
Fdiv = req->new_frequency_divider;
// only the domain master executes the actual request
if (!(RCCE_IAM == RCCE_voltage_domain_master())) return(RCCE_SUCCESS);
// if no power change request in flight, don't wait
if (!RCCE_set_power_active) {
return(RCCE_error_return(RCCE_debug_RPC, RCCE_ERROR_NO_ACTIVE_RPC_REQUEST));
}
// unset flag indicating power change is in flight
RCCE_set_power_active = 0;
if (req->release) {
return(RCCE_error_return(RCCE_debug_RPC, RCCE_ERROR_STALE_RPC_REQUEST));
}
// wait until target voltage has been reached
RC_wait_voltage(req);
RC_current_voltage_level = req->new_voltage_level;
// if we asked for a decrease in the clock divider, apply it now, after the
// required target voltage has been reached.
if (req->new_frequency_divider < req->old_frequency_divider) {
// need to set frequency divider on all tiles of the voltage domain
RCCE_set_frequency_divider(Fdiv, &new_Fdiv);
RC_current_frequency_divider = req->new_frequency_divider;
}
#ifdef POWER_DEBUG
printf("UE %d at voltage level %d, frequency divider %d\n",
RCCE_IAM, RC_current_voltage_level, RC_current_frequency_divider);
#endif
req->release = 1;
return(RCCE_SUCCESS);
}
int RC_wait_voltage(RCCE_REQUEST *req) {
//COULD USE SOME ERROR HANDLING CODE HERE
#ifndef SCC
// on the emulator we need to wait for a certain amount of time to elapse
long long target_cycles = RC_WAIT_CYCLES + req->start_cycle;
while (RC_global_clock() < target_cycles);
#else
// on SCC we simply reissue the RPC command. It will block until the previous
// command has been processed
#ifdef POWER_DEBUG
printf("UE %d writes VID word again\n", RCCE_IAM);
#endif
// do it twice, see findings by Nikolias Ioannou
*RPC_virtual_address = VID_word(req->new_voltage_level, RCCE_voltage_domain(RCCE_IAM));
*RPC_virtual_address = VID_word(req->new_voltage_level, RCCE_voltage_domain(RCCE_IAM));
#endif
return(RCCE_SUCCESS);
}
int RCCE_set_frequency_divider(int Fdiv, int *new_Fdiv) {
int ue, Vlevel;
// only the domain master executes the actual request
if (!(RCCE_IAM == RCCE_voltage_domain_master())) return(RCCE_SUCCESS);
if (RCCE_set_power_active) {
return(RCCE_error_return(RCCE_debug_RPC, RCCE_ERROR_MULTIPLE_RPC_REQUESTS));
}
if (Fdiv > RC_MAX_FREQUENCY_DIVIDER) Fdiv = RC_MAX_FREQUENCY_DIVIDER;
else
if (Fdiv < RC_MIN_FREQUENCY_DIVIDER) Fdiv = RC_MIN_FREQUENCY_DIVIDER;
// check to see if the new frequency divider is valid
Vlevel = RC_voltage_level(Fdiv);
if (RC_current_voltage_level < Vlevel || Vlevel < 0)
return(RCCE_error_return(RCCE_debug_RPC, RCCE_ERROR_FDIVIDER));
*new_Fdiv = Fdiv;
// need to set frequency divider on all tiles of the voltage domain
for (ue=0; ue<4; ue++) {
RC_set_frequency_divider(RCCE_FDOM(RCCE_ue_F_masters[ue]), Fdiv);
}
return(RCCE_SUCCESS);
}
#endif