00001
00002
00003
00004
00005
00006
00007 #include "V172X_Daq.hh"
00008
00009 #include "CAENVMElib.h"
00010 #include "RawEvent.hh"
00011 #include "Message.hh"
00012 #include "ConfigHandler.hh"
00013 #include "EventHandler.hh"
00014 #include <string>
00015 #include <time.h>
00016 #include <bitset>
00017 #include <algorithm>
00018 #include "boost/ref.hpp"
00019 #include "boost/timer.hpp"
00020 #include "boost/date_time/posix_time/posix_time_duration.hpp"
00021 #include <sstream>
00022
00023
00024 const int event_size_padding = 8;
00025
00026 enum VME_REGISTERS{
00027 VME_ChZSThresh = 0x1024,
00028 VME_ChZSNsamples = 0x1028,
00029 VME_ChTrigThresh = 0x1080,
00030 VME_ChTrigSamples = 0x1084,
00031 VME_ChStatus = 0x1088,
00032 VME_ChBuffersFull = 0x1094,
00033 VME_ChDAC = 0x1098,
00034 VME_ChannelsConfig = 0x8000,
00035 VME_BufferCode = 0x800C,
00036 VME_BufferFree = 0x8010,
00037 VME_CustomSize = 0x8020,
00038 VME_AcquisitionControl = 0x8100,
00039 VME_AcquisitionStatus = 0x8104,
00040 VME_SWTrigger = 0x8108,
00041 VME_TrigSourceMask = 0x810C,
00042 VME_TrigOutMask = 0x8110,
00043 VME_PostTriggerSetting = 0x8114,
00044 VME_FrontPanelIO = 0x811C,
00045 VME_ChannelMask = 0x8120,
00046 VME_DownsampleFactor = 0x8128,
00047 VME_EventsStored = 0x812C,
00048 VME_BoardInfo = 0x8140,
00049 VME_EventSize = 0x814C,
00050 VME_VMEControl = 0xEF00,
00051 VME_VMEStatus = 0xEF04,
00052 VME_BoardID = 0xEF08,
00053 VME_RelocationAddress = 0xEF10,
00054 VME_InterruptID = 0xEF14,
00055 VME_InterruptOnEvent = 0xEF18,
00056 VME_BLTEvents = 0xEF1C,
00057 VME_SWReset = 0xEF24,
00058 VME_SWClear = 0xEF28,
00059 };
00060
00061 V172X_Daq::V172X_Daq() : BaseDaq(), _initialized(false),
00062 _params(), _triggers(0), _vme_mutex()
00063 {
00064 ConfigHandler::GetInstance()->RegisterParameter(_params.GetDefaultKey(),
00065 _params);
00066 _handle_vme_bridge = 0;
00067 std::fill (_handle_board, _handle_board + _params.nboards, 0);
00068 }
00069
00070 V172X_Daq::~V172X_Daq()
00071 {
00072 if (_params.vme_bridge_link >= 0) CAENVME_End(_handle_vme_bridge);
00073
00074 for(int i=0; i < _params.nboards; i++)
00075 if(_params.board[i].enabled && _params.board[i].link > 0 && _params.board[i].link != _params.vme_bridge_link) CAENVME_End(_handle_board[i]);
00076 }
00077
00078 int init_link (int link, int board, bool usb, int32_t *handle) {
00079 CVErrorCodes err = CAENVME_Init(usb ? cvV1718 : cvV2718, link, board, handle);
00080 if(err != cvSuccess){
00081 Message m(ERROR);
00082 m<<"Unable to initialize CAEN VME bridge for link " << link;
00083 if(usb) m<<" on USB ";
00084 m<< ": "<<std::endl;
00085 m<<"\t"<<CAENVME_DecodeError(err)<<std::endl;
00086 return -1;
00087 }
00088 char message[100];
00089 Message(DEBUG)<<"CAEN VME bridge successfully initialized for link "
00090 << link << "!"<<std::endl;
00091 CAENVME_BoardFWRelease(*handle,message);
00092 Message(DEBUG)<<"\tFirmware Release: "<<message<<std::endl;
00093 CAENVME_DriverRelease(*handle,message);
00094 Message(DEBUG)<<"\tDriver Release: "<<message<<std::endl;
00095 Message(INFO)<< "Link " << link << " initialized on handle "
00096 << *handle <<std::endl;
00097 return 0;
00098 }
00099
00100
00101 int V172X_Daq::Initialize()
00102 {
00103 if(_initialized){
00104 Message(WARNING)<<"Reinitializing V172X_Daq..."<<std::endl;
00105 if (_params.vme_bridge_link >= 0) CAENVME_End(_handle_vme_bridge);
00106
00107 for(int i=0; i < _params.nboards; i++)
00108 if(_params.board[i].enabled && _params.board[i].link > 0 && _params.board[i].link != _params.vme_bridge_link) CAENVME_End(_handle_board[i]);
00109 }
00110
00111 if (_params.vme_bridge_link >= 0) {
00112 if (init_link (_params.vme_bridge_link, 0, false, &_handle_vme_bridge) < 0){
00113 _status=INIT_FAILURE;
00114 return -1;
00115 }
00116
00117
00118
00119 if(_params.send_start_pulse) {
00120 CAENVME_ClearOutputRegister(_handle_vme_bridge, 0xFFFF);
00121 for(int line = 0; line<5; line++){
00122 CVErrorCodes err = CAENVME_SetOutputConf(_handle_vme_bridge, (CVOutputSelect)line, cvDirect,
00123 cvActiveHigh, cvManualSW);
00124 if(err != cvSuccess){
00125 Message(ERROR)<<"Unable to configure the V2718 output registers.\n";
00126 return -2;
00127 }
00128 }
00129 }
00130 }
00131 else if (_params.send_start_pulse) {
00132 Message(WARNING)<<"send_start_pulse enabled but no bridge link present, disabling pulses" << std::endl;
00133 _params.send_start_pulse = 0;
00134 }
00135
00136 for(int i=0; i < _params.nboards; i++) {
00137 if(_params.board[i].enabled &&
00138 ( ( _params.board[i].link >= 0 &&
00139 _params.board[i].link != _params.vme_bridge_link )
00140 || _params.board[i].usb ) ){
00141 if (init_link (_params.board[i].link, _params.board[i].chainindex,
00142 _params.board[i].usb, _handle_board + i) < 0) {
00143 _status=INIT_FAILURE;
00144 return -3;
00145 }
00146 }
00147 else _handle_board[i] = _handle_vme_bridge;
00148 }
00149
00150 for(int i=0; i < _params.nboards; i++){
00151
00152 if(!_params.board[i].enabled)
00153 continue;
00154 try{
00155 if(InitializeBoard(i)){
00156 _status = INIT_FAILURE;
00157 return -5;
00158 }
00159
00160 }
00161 catch(std::exception &error)
00162 {
00163 Message(EXCEPTION)<<error.what()<<std::endl;
00164 return -6;
00165 }
00166 }
00167 _initialized = true;
00168 return Update();
00169 }
00170
00171 int V172X_Daq::InitializeBoard(int boardnum)
00172 {
00173 V172X_BoardParams& board = _params.board[boardnum];
00174 WriteVMERegister(board.address + VME_SWReset, 1, _handle_board[boardnum]);
00175 uint32_t data = ReadVMERegister(board.address+VME_BoardInfo, _handle_board[boardnum]);
00176 board.board_type = (BOARD_TYPE)(data&0xFF);
00177 board.nchans = (data>>16)&0xFF;
00178 board.mem_size = (data>>8)&0xFF;
00179 if(board.UpdateBoardSpecificVariables()){
00180 Message(CRITICAL)<<"Board "<<boardnum<<" with address "
00181 <<std::hex<<std::showbase
00182 <<board.address<<std::dec<<std::noshowbase
00183 <<" is not a V172X digitizer!"<<std::endl;
00184 _status = INIT_FAILURE;
00185 return -2;
00186 }
00187 WriteVMERegister(board.address+VME_BoardID,
00188 board.id, _handle_board[boardnum]);
00189 WriteVMERegister(board.address+VME_InterruptID,
00190 board.id, _handle_board[boardnum]);
00191 return 0;
00192 }
00193
00194 int V172X_Daq::Update()
00195 {
00196 if(!_initialized){
00197 Message(ERROR)<<"Attempt to update parameters before initializations!"
00198 <<std::endl;
00199 return -1;
00200 }
00201 if(_is_running){
00202 Message(ERROR)<<"Attempt to update parameters while in run."
00203 <<std::endl;
00204 return -2;
00205 }
00206 try{
00207 for(int iboard=0; iboard < _params.nboards; iboard++){
00208 V172X_BoardParams& board = _params.board[iboard];
00209
00210 board.downsample_factor=1;
00211 if(!board.enabled)
00212 continue;
00213
00214
00215 runinfo* info = EventHandler::GetInstance()->GetRunInfo();
00216 if(info){
00217 std::stringstream ss;
00218 ss<<"board"<<iboard<<".pre_trigger_time_us";
00219 info->SetMetadata(ss.str(), board.pre_trigger_time_us);
00220 ss.str("");
00221 ss<<"board"<<iboard<<".post_trigger_time_us";
00222 info->SetMetadata(ss.str(), board.post_trigger_time_us);
00223
00224 }
00225
00226 uint32_t channel_mask = 0;
00227 uint32_t trigger_mask = 0;
00228 uint32_t trigger_out_mask = 0;
00229
00230
00231 for(int i=0; i<board.nchans; i++){
00232 V172X_ChannelParams& channel = board.channel[i];
00233 channel_mask += (1<<i) * channel.enabled;
00234 trigger_mask += (1<<i) * channel.enable_trigger_source;
00235 trigger_out_mask += (1<<i) * channel.enable_trigger_out;
00236
00237
00238 uint32_t zs_thresh = (1<<31) * channel.zs_polarity +
00239 channel.zs_threshold;
00240 WriteVMERegister(board.address+VME_ChZSThresh+i*0x100,zs_thresh, _handle_board[iboard]);
00241
00242 uint32_t nsamp = channel.zs_thresh_time_us * board.GetSampleRate();
00243 if(nsamp >= (1<<20)) nsamp = (1<<20) -1;
00244 if(board.zs_type == ZLE){
00245
00246 if(channel.zs_pre_samps>=(1<<16)) channel.zs_pre_samps = (1<<16)-1;
00247 if(channel.zs_post_samps>=(1<<16)) channel.zs_post_samps = (1<<16)-1;
00248 uint32_t npre =
00249 std::ceil(channel.zs_pre_samps/board.stupid_size_factor);
00250 uint32_t npost =
00251 std::ceil(channel.zs_pre_samps/board.stupid_size_factor);
00252 nsamp = (npre<<16) + npost;
00253 }
00254 WriteVMERegister(board.address+ VME_ChZSNsamples+i*0x100, nsamp, _handle_board[iboard]);
00255
00256 WriteVMERegister(board.address+ VME_ChTrigThresh+i*0x100,
00257 channel.threshold, _handle_board[iboard]);
00258
00259 nsamp = std::ceil(channel.thresh_time_us * board.GetSampleRate())
00260 / board.stupid_size_factor;
00261
00262 if(nsamp >= (1<<12)) nsamp = (1<<12) - 1;
00263 WriteVMERegister(board.address+ VME_ChTrigSamples+i*0x100, nsamp, _handle_board[iboard]);
00264
00265 WriteVMERegister(board.address+ VME_ChDAC+i*0x100, channel.dc_offset, _handle_board[iboard]);
00266
00267 uint32_t status = 0x4;
00268 while( channel.enabled &&( (status&0x4) || !(status&0x2)) ){
00269
00270 status = ReadVMERegister(board.address+VME_ChStatus +i*0x100, _handle_board[iboard]);
00271
00272 }
00273 }
00274
00275 uint32_t channel_config = (1<<16) * board.zs_type +
00276 (1<<6) * board.trigger_polarity +
00277 (1<<4) +
00278 (1<<3) * board.enable_test_pattern +
00279 (1<<1) * board.enable_trigger_overlap;
00280 WriteVMERegister(board.address+ VME_ChannelsConfig, channel_config, _handle_board[iboard]);
00281
00282 WriteVMERegister(board.address+VME_BufferCode, board.GetBufferCode(), _handle_board[iboard]);
00283
00284
00285 WriteVMERegister(board.address+ VME_CustomSize,
00286 board.GetCustomSizeSetting(), _handle_board[iboard]);
00287
00288
00289 uint32_t acq_control =
00290 (1<<3) * board.count_all_triggers +
00291 _params.send_start_pulse;
00292
00293 WriteVMERegister(board.address+VME_AcquisitionControl,acq_control, _handle_board[iboard]);
00294 board.acq_control_val = acq_control;
00295
00296 if(board.local_trigger_coincidence >7)
00297 board.local_trigger_coincidence = 7;
00298 trigger_mask += (1<<31) * board.enable_software_trigger
00299 + (1<<30) * board.enable_external_trigger
00300 + (1<<24) * board.local_trigger_coincidence;
00301 WriteVMERegister(board.address+ VME_TrigSourceMask, trigger_mask, _handle_board[iboard]);
00302
00303 trigger_out_mask += (1<<31) * board.enable_software_trigger_out +
00304 (1<<30) * board.enable_external_trigger_out;
00305 WriteVMERegister(board.address+ VME_TrigOutMask, trigger_out_mask, _handle_board[iboard]);
00306
00307
00308 WriteVMERegister(board.address+VME_PostTriggerSetting,
00309 board.GetPostTriggerSetting(), _handle_board[iboard]);
00310
00311 WriteVMERegister(board.address+ VME_FrontPanelIO,
00312 board.signal_logic + (1<<6), _handle_board[iboard]);
00313
00314 WriteVMERegister(board.address+ VME_ChannelMask, channel_mask,
00315 _handle_board[iboard]);
00316
00317 uint32_t vme_control = (1<<5) * _params.align64 +
00318 (1<<4) +
00319 (1<<3) +
00320 1;
00321 WriteVMERegister(board.address+ VME_VMEControl, vme_control, _handle_board[iboard]);
00322
00323 WriteVMERegister(board.address+VME_InterruptOnEvent, 0, _handle_board[iboard]);
00324 WriteVMERegister(board.address+VME_BLTEvents, 1, _handle_board[iboard]);
00325
00326 uint32_t status = 0;
00327 int count = 0;
00328 while( !((status&0x100) && (status&0xc0)) ){
00329 status = ReadVMERegister(board.address+VME_AcquisitionStatus, _handle_board[iboard]);
00330 if(count++ > 500){
00331 Message(ERROR)<<"Unable to initialize board "<<iboard<<" at address "
00332 <<std::hex<<board.address<<std::dec<<"\n";
00333 return 1;
00334 }
00335 }
00336
00337 }
00338
00339
00340
00341
00342 Message(DEBUG)<<"The expected event size is "<<_params.GetEventSize()
00343 <<" bytes."<<std::endl;
00344
00345 time_t now = time(0);
00346 while(time(0) - now < 2) {}
00347 }
00348 catch(...){
00349 return -3;
00350 }
00351 return 0;
00352 }
00353
00354
00355 bool DataAvailable(uint32_t status)
00356 {
00357 return (status & 0x8);
00358 }
00359
00360 void V172X_Daq::DataAcquisitionLoop()
00361 {
00362 if(!_initialized) Initialize();
00363
00364 _triggers = 0;
00365 std::vector<uint32_t> acq_status(_params.enabled_boards);
00366
00367 try{
00368 std::vector<uint32_t> acq_write;
00369 for(int i=0; i<_params.nboards; i++){
00370 if(_params.board[i].enabled){
00371 acq_write.push_back(_params.board[i].acq_control_val+0x4);
00372 }
00373 }
00374 WriteVMERegisters(VME_SWClear,1);
00375 WriteVMERegisters(VME_AcquisitionControl,&(acq_write[0]));
00376 if(_params.send_start_pulse)
00377 CAENVME_SetOutputRegister(_handle_vme_bridge,
00378 cvOut0Bit | cvOut1Bit | cvOut2Bit |
00379 cvOut3Bit | cvOut4Bit );
00380 }
00381 catch(std::exception& e){
00382 Message(ERROR)<<"Unable to arm the board for run!\n";
00383 _initialized=false;
00384 _status=INIT_FAILURE;
00385 return;
00386 }
00387 int32_t irq_handle = _handle_vme_bridge;
00388 if (_params.vme_bridge_link)
00389 for(int i=0; i < _params.nboards; i++) {
00390 irq_handle = _handle_board[i];
00391 if (_params.board[i].enabled && _params.board[i].link >= 0) break;
00392 }
00393
00394
00395
00396
00397 bool use_interrupt = true;
00398 for(int i=0; i<_params.nboards; i++){
00399 if(_params.board[i].enabled && _params.board[i].usb)
00400 use_interrupt = false;
00401 }
00402
00403
00404 while(_is_running ){
00405
00406 CVErrorCodes err = cvTimeoutError;
00407
00408 if(use_interrupt){
00409
00410 CAENVME_IRQEnable(irq_handle,0xFF);
00411 err = CAENVME_IRQWait(irq_handle,0xFF,
00412 _params.trigger_timeout_ms);
00413 CAENVME_IRQDisable(irq_handle,0xFF);
00414 }
00415 else{
00416 for(int i=0; i<_params.nboards; i++){
00417 if(!_params.board[i].enabled) continue;
00418
00419 if(DataAvailable(ReadVMERegister(_params.board[i].address+
00420 VME_AcquisitionStatus,
00421 _handle_board[i])) ){
00422 err = cvSuccess;
00423 break;
00424 }
00425 }
00426 }
00427
00428
00429 switch(err){
00430 case cvSuccess:
00431 break;
00432 case cvGenericError:
00433 Message(DEBUG)<<"Generic error occurred. Probably just a timeout...\n";
00434
00435 case cvTimeoutError:
00436 if(!use_interrupt){
00437
00438 boost::this_thread::sleep(
00439 boost::posix_time::millisec(_params.trigger_timeout_ms));
00440 }
00441 if(_params.auto_trigger){
00442 Message(DEBUG)<<"Triggering...\n";
00443 WriteVMERegisters(VME_SWTrigger,1);
00444 }
00445 else
00446 Message(DEBUG)<<"Waiting for trigger..."<<std::endl;
00447 continue;
00448 break;
00449 default:
00450 Message(ERROR)<<"Unknown error waiting for trigger interrupt\n";
00451 break;
00452 }
00453
00454
00455
00456 RawEventPtr next_event(new RawEvent);
00457 size_t blocknum =
00458 next_event->AddDataBlock(RawEvent::CAEN_V172X,
00459 _params.event_size_bytes+event_size_padding);
00460 unsigned char* buffer = next_event->GetRawDataBlock(blocknum);
00461 const uint32_t UNSET_EVENT_COUNTER = 0xFFFFFFFF;
00462 uint32_t event_counter = UNSET_EVENT_COUNTER;
00463
00464 int data_transferred = 0;
00465 for(int i=0; i<_params.nboards; i++){
00466 if(!_params.board[i].enabled) continue;
00467
00468
00469 int tries = 0;
00470 const int maxtries = 50;
00471 while(!DataAvailable(ReadVMERegister(_params.board[i].address+
00472 VME_AcquisitionStatus, _handle_board[i])) &&
00473 tries++ < maxtries) {}
00474 if(tries >= maxtries){
00475 Message(DEBUG)<<"No trigger received on board "<<i<<"\n";
00476 continue;
00477 }
00478
00479 int this_dl_size = 0;
00480 tries=0;
00481 CVErrorCodes err = cvSuccess;
00482 while(this_dl_size == 0 && ++tries<maxtries ){
00483 err =
00484 CAENVME_FIFOBLTReadCycle(_handle_board[i], _params.board[i].address,
00485 buffer + data_transferred,
00486 _params.board[i].event_size_bytes,
00487 cvA32_U_MBLT, cvD64, &this_dl_size);
00488 if(err != cvSuccess && err != cvBusError){
00489 Message(ERROR)<<"Error generated while downloading event from board"
00490 <<i<<": "<<CAENVME_DecodeError(err)<<"\n";
00491 continue;
00492 }
00493 }
00494
00495 if(this_dl_size<=0){
00496 Message(ERROR)<<"0 bytes downloaded for board "<<i<<std::endl;
00497 Message(DEBUG)<<"Events stored on this board: "
00498 <<ReadVMERegister(_params.board[i].address+
00499 VME_EventsStored, _handle_board[i])<<"\n";
00500 uint32_t out;
00501 out = ReadVMERegister(_params.board[i].address+VME_VMEStatus,
00502 _handle_board[i]);
00503 Message(DEBUG)<<"VME status:"
00504 <<"\n\tBERR flag: "<< (out&4)
00505 <<"\n\tOutput buffer full: "<< (out&2)
00506 <<"\n\tData ready: "<< (out&1)<<"\n";
00507 out = ReadVMERegister(_params.board[i].address+VME_AcquisitionStatus,
00508 _handle_board[i]);
00509 Message(DEBUG)<<"Acquisition status:"
00510 <<"\n\tReady for acquisition: "<< (out&256)
00511 <<"\n\tPLL Status: "<< (out&128)
00512 <<"\n\tPLL Bypass: "<< (out&64)
00513 <<"\n\tClock source: "<< (out&32)
00514 <<"\n\tEvents full: "<< (out&16)
00515 <<"\n\tEvent ready: "<< (out&8)
00516 <<"\n\t Run on: "<< (out&4) <<"\n";
00517 out = ReadVMERegister(_params.board[i].address+VME_EventSize,
00518 _handle_board[i]);
00519 Message(DEBUG)<<"Next event size: "<<out<<"\n";
00520 Message(DEBUG)<<"Expected event size "
00521 <<_params.board[i].event_size_bytes/4<<"\n";
00522
00523
00524
00525 WriteVMERegister(_params.board[i].address+VME_BufferFree,1,
00526 _handle_board[i]);
00527 Message(DEBUG)<<"Events stored on this board: "
00528 <<ReadVMERegister(_params.board[i].address+
00529 VME_EventsStored, _handle_board[i])
00530 <<"\n";
00531 WriteVMERegister(_params.board[i].address+VME_BufferFree,2,
00532 _handle_board[i]);
00533 Message(DEBUG)<<"Events stored on this board: "
00534 <<ReadVMERegister(_params.board[i].address+
00535 VME_EventsStored, _handle_board[i])
00536 <<"\n";
00537 Message(ERROR)<<"Boards don't usually recover from this error! "
00538 <<"Aborting!\n";
00539 _status = COMM_ERROR;
00540 _is_running = false;
00541 break;
00542 }
00543 else{
00544
00545
00546
00547
00548 long ev_size = (*((uint32_t*)(buffer+data_transferred)) &
00549 0x0FFFFFFF) * sizeof(uint32_t);
00550 if(std::abs(ev_size - this_dl_size) > 5){
00551 Message(WARNING)<<"Event size does not match download count!\n\t"
00552 <<"Event size: "<<ev_size<<"; download size: "
00553 <<this_dl_size<<"; requested download "
00554 <<_params.board[i].event_size_bytes<<std::endl;
00555 Message(ERROR)<<"Boards don't usually recover from this error! "
00556 <<"Aborting!\n";
00557 _status = COMM_ERROR;
00558 _is_running = false;
00559 break;
00560 }
00561
00562 uint32_t evct = (((uint32_t*)(buffer+data_transferred))[2])&0xFFFFFF;
00563 if(event_counter == UNSET_EVENT_COUNTER)
00564 event_counter = evct;
00565 else if(evct != event_counter){
00566 Message(CRITICAL)<<"Mismatched event ID on board "<<i
00567 <<"; received "<<evct<<", expected "<<event_counter
00568 <<"; Aboring run\n";
00569 _status = GENERIC_ERROR;
00570 _is_running=false;
00571 break;
00572 }
00573
00574 data_transferred += ev_size;
00575
00576 }
00577 }
00578 if(GetStatus() != NORMAL){
00579 _is_running = false;
00580 break;
00581 }
00582 if(data_transferred <= 0){
00583 Message(ERROR)<<"No boards transferred usable data this event!\n";
00584 continue;
00585 }
00586 else{
00587 _triggers++;
00588 next_event->SetDataBlockSize(blocknum, data_transferred);
00589 PostEvent(next_event);
00590 }
00591 }
00592
00593
00594 if(_params.send_start_pulse)
00595 CAENVME_ClearOutputRegister(_handle_vme_bridge, 0xFFFF);
00596 for(int i=0; i<_params.nboards; i++){
00597 if(_params.board[i].enabled){
00598 WriteVMERegister(_params.board[i].address+ VME_AcquisitionControl,
00599 _params.board[i].acq_control_val, _handle_board[i]);
00600 WriteVMERegister(_params.board[i].address+VME_SWClear,0x1, _handle_board[i]);
00601 }
00602 }
00603 WriteVMERegisters(VME_SWReset,1);
00604 Message(DEBUG)<<_triggers<<" total triggers downloaded."<<std::endl;
00605 }