-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathtransport_skarab.py
4606 lines (3890 loc) · 188 KB
/
transport_skarab.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import socket
import math
import select
import logging
import struct
import time
import os
import random
import contextlib
from threading import Lock
import skarab_definitions as sd
import skarab_fileops as skfops
import CasperLogHandlers
from transport import Transport
from network import IpAddress
__author__ = 'tyronevb'
__date__ = 'April 2016'
# region -- Custom Errors and Return Values for SKARAB --
class SkarabSendPacketError(ValueError):
pass
class SkarabUploadChecksumMismatch(ValueError):
pass
class SkarabSdramError(RuntimeError):
pass
class SkarabInvalidResponse(ValueError):
pass
class SkarabReadFailed(ValueError):
pass
class SkarabWriteFailed(ValueError):
pass
class SkarabSequenceSetError(RuntimeError):
pass
class SkarabUnknownDeviceError(ValueError):
pass
class SkarabResponseNotReceivedError(RuntimeError):
pass
class SkarabReorderWarning(ValueError):
pass
class SkarabReorderError(ValueError):
pass
class SkarabSpeadWarning(ValueError):
pass
class SkarabSpeadError(ValueError):
pass
class SkarabInvalidHostname(RuntimeError):
pass
class InvalidDeviceType(ValueError):
pass
class SkarabFanControllerClearError(ValueError):
pass
class NonVolatileLogRetrievalError(ValueError):
pass
class SkarabProcessorVersionError(ValueError):
pass
class NetworkConfigurationError(ValueError):
pass
# endregion
class SkarabTransport(Transport):
"""
The network transport for a SKARAB-type interface.
"""
def __init__(self, **kwargs):
"""
Initialized SKARAB FPGA object
:param host: IP Address of the targeted SKARAB Board
:param parent_fpga: Instance of parent_fpga
:param timeout: Send packet timeout in seconds,
defaults to CONTROL_RESPONSE_TIMEOUT
in skarab_definitions.py
:param retries: Send packet retries, defaults to
CONTROL_RESPONSE_RETRIES in skarab_definitions.py
:param blocking: True (default)/False. If True a SKARAB comms
check will be performed. If False only the
instance will be created.
"""
Transport.__init__(self, **kwargs)
try:
# Entry point is always via casperfpga.CasperFpga
self.parent = kwargs['parent_fpga']
self.logger = self.parent.logger
except KeyError:
errmsg = 'parent_fpga argument not supplied when creating skarab'
# Pointless trying to log to a logger
raise RuntimeError(errmsg)
new_connection_msg = '*** NEW CONNECTION MADE TO {} ***'.format(self.host)
self.logger.debug(new_connection_msg)
try:
self.timeout = kwargs['timeout']
except KeyError:
self.timeout = sd.CONTROL_RESPONSE_TIMEOUT
try:
self.retries = kwargs['retries']
except KeyError:
self.retries = sd.CONTROL_RESPONSE_RETRIES
try:
self.blocking = kwargs['blocking']
except KeyError:
self.blocking = True
# sequence number for control packets
self._seq_num = None
self.reset_seq_num()
# create tuple for ethernet control packet address
self.skarab_eth_ctrl_addr = (
self.host, sd.ETHERNET_CONTROL_PORT_ADDRESS)
# create tuple for fabric packet address
self.skarab_fpga_addr = (self.host, sd.ETHERNET_FABRIC_PORT_ADDRESS)
# flag for keeping track of SDRAM state
self._sdram_programmed = False
# dict for sensor data, empty at initialization
self.sensor_data = {}
# create, and connect to, a socket for the skarab object
self._skarab_control_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
self._skarab_control_sock.connect(self.skarab_eth_ctrl_addr)
except socket.gaierror:
errmsg = 'Hostname invalid, check leases or resource-list'
self.logger.error(errmsg)
raise SkarabInvalidHostname(errmsg)
self._skarab_control_sock.setblocking(0)
self._lock=Lock()
# check if connected to host
if self.blocking:
if self.is_connected():
self.logger.info('Port({}) created & connected.'.format(
sd.ETHERNET_CONTROL_PORT_ADDRESS))
else:
self.logger.error('Error connecting to {}: port{}'.format(self.host,
sd.ETHERNET_CONTROL_PORT_ADDRESS))
# self.image_chunks, self.local_checksum = None, None
# TODO - add the one_gbe
# self.gbes = []
# self.gbes.append(FortyGbe(self, 0))
# # self.gbes.append(FortyGbe(self, 0, 0x54000 - 0x4000))
def _update_response_timeout(self, timeout):
"""
Method to update the timeout when waiting for responses from SKARABs
:return:
"""
self.timeout = timeout
@staticmethod
def test_host_type(host_ip):
"""
Is a given IP assigned to a SKARAB?
:param host_ip:
"""
with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as sctrl_sock:
sctrl_sock.setblocking(0)
skarab_eth_ctrl_port = (host_ip, sd.ETHERNET_CONTROL_PORT_ADDRESS)
request_object = sd.ReadRegReq(sd.BOARD_REG, sd.C_RD_VERSION_ADDR)
request_payload = request_object.create_payload(0xffff)
sctrl_sock.sendto(request_payload, skarab_eth_ctrl_port)
data_ready = select.select([sctrl_sock], [], [], 1)
if len(data_ready[0]) > 0:
# self.logger.debug('%s seems to be a SKARAB' % host_ip)
return True
return False
def is_connected(self,
timeout=None,
retries=None):
"""
'ping' the board to see if it is connected and running.
Tries to read a register
:return: Boolean - True/False - Succes/Fail
"""
if timeout is None: timeout=self.timeout
if retries is None: retries=self.retries
try:
data = self.read_board_reg(sd.C_RD_VERSION_ADDR, retries=retries,
timeout=timeout)
return True if data else False
except ValueError as vexc:
self.logger.debug('Skarab is not connected: %s' % vexc.message)
return False
def is_running(self):
"""
Is the FPGA programmed and running a toolflow image?
:return: True or False
"""
[golden_img, multiboot, version] = self.get_virtex7_firmware_version()
if golden_img == 0 and multiboot == 0:
return True
return False
def loopbacktest(self, iface, timeout=None,
retries=None):
"""
Run the loopback test.
:param iface:
"""
if timeout is None: timeout=self.timeout
if retries is None: retries=self.retries
request = sd.DebugLoopbackTestReq(iface, 0x77)
response = self.send_packet(request, timeout=timeout, retries=retries)
raise RuntimeError('Not yet tested')
def _get_device_address(self, device_name):
# map device name to address, if can't find, bail
if device_name in self.memory_devices:
return self.memory_devices[device_name].address
elif (type(device_name) == int) and (0 <= device_name < 2 ** 32):
# also support absolute address values
self.logger.warning('Absolute address given: 0x%06x' % device_name)
return device_name
errmsg = 'Could not find device: %s' % device_name
self.logger.error(errmsg)
raise SkarabUnknownDeviceError(errmsg)
def read(self, device_name, size, offset=0, use_bulk=True,
timeout=None,
retries=None):
"""
Read size-bytes of binary data with carriage-return escape-sequenced.
:param device_name: name of memory device from which to read
:param size: how many bytes to read
:param offset: start at this offset, offset in bytes
:param use_bulk: use the bulk read function
:param timeout: value in seconds to wait before aborting instruction
- Default value is None, uses initialised value
:param retries: value specifying number of retries should instruction fail
- Default value is None, uses initialised value
:return: binary data string
"""
if timeout is None: timeout=self.timeout
if retries is None: retries=self.retries
if (size > 4) and use_bulk:
# use a bulk read if more than 4 bytes are requested
return self._bulk_read(device_name, size, offset)
addr = self._get_device_address(device_name)
# can only read 4 bytes at a time
# work out how many reads we require, and from where
offset_bytes = int(offset / 4) * 4
offset_diff = offset - offset_bytes
num_bytes_corrected = size + offset_diff
num_reads = int(math.ceil(num_bytes_corrected / 4.0))
addr_start = addr + offset - offset_diff
# self.logger.info('size(%i) offset(%i) addr(0x%06x) => '
# 'offset_corrected(%i) size_corrected(%i) '
# 'addr_start(0x%06x) numreads(%i)' % (
# size, offset, addr, offset_bytes, num_bytes_corrected,
# addr_start, num_reads))
# address to read is starting address plus offset
data = ''
for readctr in range(num_reads):
addr_high, addr_low = self.data_split_and_pack(addr_start)
request = sd.ReadWishboneReq(addr_high, addr_low)
response = self.send_packet(request, timeout=timeout, retries=retries)
# merge high and low binary data for the current read
read_low = struct.pack('!H', response.packet['read_data_low'])
read_high = struct.pack('!H', response.packet['read_data_high'])
new_read = read_high + read_low
# append current read to read data
data += new_read
# increment addr_start by four
addr_start += 4
# return the number of bytes requested
return data[offset_diff: offset_diff + size]
def _bulk_read_req(self, address, words_to_read,
timeout=None,
retries=None):
"""
:param address: the address at which to read
:param words_to_read: how many 32-bit words should be read
:return: binary data string
"""
if timeout is None: timeout=self.timeout
if retries is None: retries=self.retries
# self.logger.info('reading @ 0x%06x - %i words' % (address, words_to_read))
if words_to_read > sd.MAX_READ_32WORDS:
raise RuntimeError('Cannot read more than %i words - '
'asked for %i' % (sd.MAX_READ_32WORDS,
words_to_read))
start_addr_high, start_addr_low = self.data_split_and_pack(address)
# the uBlaze will only read as much as you tell it to, but will
# return the the whole lot, zeros in the rest
request = sd.BigReadWishboneReq(start_addr_high, start_addr_low,
words_to_read)
response = self.send_packet(request, timeout=timeout, retries=retries)
if response is None:
errmsg = 'Bulk read failed.'
raise SkarabReadFailed(errmsg)
# check if wishbone read command timed out
if response.packet['number_of_reads'] \
== sd.BIG_WISHBONE_READ_ERROR_CODE:
errmsg = 'Wishbone timeout. Address 0x{:x}'.format(address)
raise SkarabReadFailed(errmsg)
# response.read_data is a list of 16-bit words, pack it
read_data = response.packet['read_data'][0:words_to_read*2]
return struct.pack('>%iH' % len(read_data), *read_data)
def _bulk_read(self, device_name, size, offset=0):
"""
Read size-bytes of binary data with carriage-return escape-sequenced.
:param device_name: name of memory device from which to read
:param size: how many bytes to read
:param offset: start at this offset, offset in bytes
:return: binary data string
"""
addr = self._get_device_address(device_name)
# self.logger.info('addr(0x%06x) size(%i) offset(%i)' % (addr, size,
# offset))
bounded_offset = int(math.floor(offset / 4.0) * 4.0)
offset_diff = offset - bounded_offset
# self.logger.info('bounded_offset(%i)' % bounded_offset)
addr += bounded_offset
size += offset_diff
# self.logger.info('offset_addr(0x%06x) offset_size(%i)' % (addr, size))
num_words_to_read = int(math.ceil(size / 4.0))
maxreadwords = 1.0 * sd.MAX_READ_32WORDS
num_reads = int(math.ceil(num_words_to_read / maxreadwords))
# self.logger.info('words_to_read(0x%06x) loops(%i)' % (num_words_to_read,
# num_reads))
data = ''
data_left = num_words_to_read
for rdctr in range(num_reads):
to_read = (sd.MAX_READ_32WORDS if data_left > sd.MAX_READ_32WORDS
else data_left)
data += self._bulk_read_req(addr, to_read)
data_left -= sd.MAX_READ_32WORDS
addr += to_read * 4
# self.logger.info('returning data[%i:%i]' % (offset_diff, size))
# return the number of bytes requested
return data[offset_diff: size]
def _bulk_write_req(self, address, data, words_to_write,
timeout=None,
retries=None):
"""
Unchecked data write. Maximum of 1988 bytes per transaction
:param address: memory device to which to write
:param data: byte string to write
:param words_to_write: number of 32-bit words to write
:return: number of 32-bit writes done
"""
if timeout is None: timeout=self.timeout
if retries is None: retries=self.retries
if words_to_write > sd.MAX_WRITE_32WORDS:
raise RuntimeError('Cannot write more than %i words - '
'asked to write %i' % (sd.MAX_WRITE_32WORDS,
words_to_write))
start_addr_high, start_addr_low = self.data_split_and_pack(address)
self.logger.debug('\nAddress High: {}\nAddress Low: {}'
'\nWords To Write: {}'.format(repr(start_addr_high),
repr(start_addr_low),
words_to_write))
request = sd.BigWriteWishboneReq(start_addr_high,
start_addr_low, data, words_to_write)
response = self.send_packet(request, timeout=timeout, retries=retries)
if response is None:
errmsg = 'Bulk write failed. No response from SKARAB.'
raise SkarabWriteFailed(errmsg)
if response.packet['number_of_writes_done'] != words_to_write:
errmsg = 'Bulk write failed. Not all words written.'
raise SkarabWriteFailed(errmsg)
# check if wishbone command timed out
if response.packet['error_status']:
errmsg = 'Wishbone timeout. Address 0x{:x}'.format(address)
raise SkarabWriteFailed(errmsg)
self.logger.debug('Number of writes dones: %d' %
response.packet['number_of_writes_done'])
return response.packet['number_of_writes_done']
def _bulk_write(self, device_name, data, offset):
"""
Data write. Supports > 4 bytes written per transaction.
:param device_name: memory device to which to write
:param data: byte string to write
:param offset: the offset, in bytes, at which to write
"""
# TODO: writing data not bounded to 32-bit words
# will have to read back the data and then apply a mask with the new
# data
# i.e. have 0X01ABCDEF, want to write 0xFF to the 1st bytes
# need to read back 0x01ABCDEF, then mask the first byte ONLY
# and write back 0xFFABCDEF, for now, only support 32-bit boundary
address = self._get_device_address(device_name)
size = len(data) # number of bytes in the write data
bounded_offset = int(math.floor(offset / 4.0) * 4.0)
offset_diff = offset - bounded_offset
address += bounded_offset
size += offset_diff
num_words_to_write = int(math.ceil(size / 4.0))
max_write_words = 1.0 * sd.MAX_WRITE_32WORDS
num_writes = int(math.ceil(num_words_to_write / max_write_words))
self.logger.debug('words_to_write(%i) loops(%i)' % (num_words_to_write,
num_writes))
write_data_left = num_words_to_write
data_start = 0
number_of_writes_done = 0
for wrctr in range(num_writes):
self.logger.debug('In write loop %i' % wrctr)
# determine the number of 32-bit words to write
to_write = (sd.MAX_WRITE_32WORDS if write_data_left >
sd.MAX_WRITE_32WORDS
else write_data_left)
self.logger.debug('words to write ..... %i' % to_write)
# get the data that is to be written in the next transaction
write_data = data[data_start: data_start + to_write*4]
self.logger.debug('Write Data Size: %i' % (len(write_data)/4))
if to_write < sd.MAX_WRITE_32WORDS:
# if writing less than the max number of words we need to pad
# to the request packet size
padding = (sd.MAX_READ_32WORDS - to_write)
self.logger.debug('we are padding . . . %i . . . 32-bit words . . '
'.' % padding)
write_data += '\x00\x00\x00\x00' * padding
number_of_writes_done += self._bulk_write_req(address, write_data,
to_write)
write_data_left -= to_write
# increment address and point to start of next 32-bit word
address += to_write * 4
data_start += to_write * 4
self.logger.debug('Number of writes dones: %d' % number_of_writes_done)
if number_of_writes_done != num_words_to_write:
errmsg = 'Bulk write failed. Only %i . . . of %i . . . 32-bit ' \
'words written' % (number_of_writes_done,
num_words_to_write)
raise SkarabWriteFailed(errmsg)
def read_byte_level(self, device_name, size, offset=0,
timeout=None,
retries=None):
"""
Byte-level read. Sorts out reads overlapping registers, and
reading specific bytes.
Read size-bytes of binary data with carriage-return escape-sequenced.
:param device_name: name of memory device from which to read
:param size: how many bytes to read
:param offset: start at this offset
:param timeout: value in seconds to wait before aborting instruction
- Default value is None, uses initialised value
:param retries: value specifying number of retries should instruction fail
- Default value is None, uses initialised value
:return: binary data string
"""
if timeout is None: timeout=self.timeout
if retries is None: retries=self.retries
# can only read 32-bits (4 bytes) at a time
# work out how many reads we require, each read req reads a 32-bit reg
# need to determine how many registers need to be read
num_reads = int(math.ceil((offset + size) / 4.0))
# string to store binary data read
data = ''
# address to read is starting address plus offset
addr = device_name + offset
for readctr in range(num_reads):
# get correct address and pack into binary format
# TODO: sort out memory mapping of device_name
addr_high, addr_low = self.data_split_and_pack(addr)
request = sd.ReadWishboneReq(addr_high, addr_low)
response = self.send_packet(request, timeout=timeout, retries=retries)
# merge high and low binary data for the current read
read_high = struct.pack('!H', response.packet['read_data_high'])
read_low = struct.pack('!H', response.packet['read_data_low'])
new_read = read_high + read_low
# append current read to read data
data += new_read
# increment addr by 4 to read the next 4 bytes (next 32-bit reg)
addr += 4
# return the number of bytes requested
return data[offset:offset + size]
def blindwrite(self, device_name, data, offset=0, use_bulk=True,
timeout=None,
retries=None):
"""
Unchecked data write.
:param device_name: the memory device to which to write
:param data: the byte string to write
:param offset: the offset, in bytes, at which to write
:param use_bulk: use the bulk write function
:param timeout: value in seconds to wait before aborting instruction
- Default value is None, uses initialised value
:param retries: value specifying number of retries should instruction fail
- Default value is None, uses initialised value
"""
if timeout is None: timeout=self.timeout
if retries is None: retries=self.retries
assert (type(data) == str), 'Must supply binary packed string data'
assert (len(data) % 4 == 0), 'Must write 32-bit-bounded words'
assert (offset % 4 == 0), 'Must write 32-bit-bounded words'
if (len(data) > 4) and use_bulk:
# use a bulk write if more than 4 bytes are to be written
self._bulk_write(device_name, data, offset)
else:
# map device name to address, if can't find, bail
addr = self._get_device_address(device_name)
# split the data into two 16-bit words
data_high = data[:2]
data_low = data[2:]
addr += offset
addr_high, addr_low = self.data_split_and_pack(addr)
request = sd.WriteWishboneReq(addr_high, addr_low,
data_high, data_low)
self.send_packet(request, timeout=timeout, retries=retries)
def deprogram(self):
"""
Deprogram the FPGA.
This actually reboots & boots from the Golden Image
"""
# trigger reboot of FPGA
self.reboot_fpga()
self.logger.info('Skarab deprogrammed okay')
def program_from_flash(self):
"""
Program the FPGA from flash memory.
This is achieved with a reboot of the board.
The SKARAB boots from flash on start up.
"""
self.reboot_fpga()
def boot_from_sdram(self):
"""
Triggers a reboot of the Virtex7 FPGA and boot from SDRAM.
"""
# check if sdram was programmed prior
if not self._sdram_programmed:
errmsg = 'SDRAM not programmed.'
self.logger.error(errmsg)
raise SkarabSdramError(errmsg)
# trigger reboot
self._complete_sdram_configuration()
# self.logger.info('Booting from SDRAM.')
# clear sdram programmed flag
self._sdram_programmed = False
# still update programming info
self.prog_info['last_programmed'] = self.prog_info['last_uploaded']
self.prog_info['last_uploaded'] = ''
def upload_to_ram(self, filename, verify=True, chunk_size=1988):
"""
Upload a bitstream to the SKARAB via the wishone --> SDRAM interface
:param filename: fpga image to upload
:param verify: calculate the hash of the local file and compare it
to the stored one.
:return: Boolean - True/False - Success/Fail
"""
# Make sure filename isn't empty
if filename == '' or filename is None:
# Problem
errmsg = 'Filename not specified!'
self.logger.error(errmsg)
raise ValueError(errmsg)
# else: Check if the file exists
abs_path = os.path.abspath(filename)
if not os.path.exists(abs_path):
# Problem
errmsg = '{} does not exist'.format(filename)
self.logger.error(errmsg)
raise ValueError(errmsg)
# else: Continue!
upload_time = skfops.upload_to_ram_progska(filename, [self.parent], chunk_size)
self.logger.debug('Uploaded bitstream in %.1f seconds.' % upload_time)
return upload_time
def check_running_firmware(self, timeout=None, retries=None):
"""
Check what image is running on the FPGA and its corresponding
firmware version.
:param timeout: value in seconds to wait before aborting instruction
- Default value is None, uses initialised value
:param retries: value specifying number of retries should instruction fail
- Default value is None, uses initialised value
:return: Tuple - (Boolean, String) where:
-> Boolean is True if Toolflow Image, False otherwise
-> String is the firmware version
"""
if timeout is None: timeout=self.timeout
if retries is None: retries=self.retries
try:
[golden_image, multiboot, firmware_version] = \
self.get_virtex7_firmware_version(timeout=timeout, retries=retries)
if golden_image == 0 and multiboot == 0:
return True, firmware_version
elif golden_image == 1 and multiboot == 0:
self.logger.error(
'Skarab is back up, but fell back to golden image with '
'firmware version %s' % firmware_version)
return False, firmware_version
elif golden_image == 0 and multiboot == 1:
self.logger.error(
'Skarab is back up, but fell back to multiboot image with '
'firmware version %s' % firmware_version)
return False, firmware_version
else:
self.logger.error(
'Skarab is back up, but unknown image with firmware '
'version number %s' % firmware_version)
return False, firmware_version
except SkarabSendPacketError as err:
self.logger.warning('Skarab {} unreachable: {}'.format(self.host,
err.message))
return False, '0.0'
def upload_to_ram_and_program(self, filename, port=-1, timeout=60,
wait_complete=True, skip_verification=False,
**kwargs):
"""
Uploads an FPGA image to the SDRAM, and triggers a reboot to boot
from the new image.
*** WARNING: Do NOT attempt to upload a BSP/Flash image to the SDRAM.
:param filename: fpga image to upload (currently supports bin, bit
and hex files)
:param port: the port to use on the rx end, -1 means a random port
:param timeout: how long to wait, seconds
:param wait_complete - wait for the board to boot after programming
:param skip_verification - do not verify the image after upload
:return: Boolean - True/False - Succes/Fail
"""
# check if a chunk size was specified, else default to 1988
if 'chunk_size' in kwargs.keys():
chunk_size = kwargs['chunk_size']
else:
# default to a chunk size of 1988
chunk_size = 1988
try:
upload_time = self.upload_to_ram(filename, not skip_verification, chunk_size)
except:
self.logger.error('Failed to program.')
raise
if not wait_complete:
self.logger.debug('Returning immediately after programming.')
return True
self.boot_from_sdram()
# wait for board to come back up
timeout = timeout + time.time()
reboot_start_time = time.time()
while timeout > time.time():
if self.is_connected(retries=1):
# # configure the mux back to user_date mode
# self.config_prog_mux(user_data=1)
result, firmware_version = self.check_running_firmware()
if result:
reboot_time = time.time() - reboot_start_time
self.logger.info(
'Skarab is back up, in %.1f seconds (%.1f + %.1f) with FW ver '
'%s' % (upload_time + reboot_time, upload_time, reboot_time,
firmware_version))
return True
else:
return False
time.sleep(0.1)
self.logger.error('Skarab has not come back after programming')
return False
def clear_sdram(self):
"""
Clears the last uploaded image from the SDRAM.
Clears sdram programmed flag.
"""
# clear sdram and ethernet counters
self.sdram_reconfigure(clear_sdram=True, clear_eth_stats=True)
# clear sdram programmed flag
self._sdram_programmed = False
# clear prog_info for last uploaded
self.prog_info['last_uploaded'] = ''
self.logger.debug('SDRAM for host {host} '
'cleared'.format(host=self.host))
@staticmethod
def data_split_and_pack(data):
"""
Splits 32-bit data into 2 16-bit words:
* dataHigh: most significant 2 bytes of data
* dataLow: least significant 2 bytes of data
Also packs the data into a binary string for network transmission
:param data: 32 bit data to be split
:return: Tuple - dataHigh, dataLow (packed into binary data string)
"""
packer = struct.Struct('!I')
packed_data = packer.pack(data)
data_high = packed_data[:2]
data_low = packed_data[-2:]
return data_high, data_low
@staticmethod
def data_unpack_and_merge(data_high, data_low):
"""
Given 2 16-bit words (dataHigh, dataLow), merges the
data into a 32-bit word
:param data_high: most significant 2 bytes of data
:param data_low: least significant 2 bytes of data
:return: unpacked 32-bit data (as a native Python type)
"""
# pack the two words to facilitate easy merging
packer = struct.Struct('!H')
data_high = packer.pack(data_high)
data_low = packer.pack(data_low)
# merge the data (as a packed string of bytes)
data = data_high + data_low
# unpacker for the 32-bit string of bytes
unpacker = struct.Struct('!I')
return unpacker.unpack(data)[0]
def reset_seq_num(self):
with Lock():
self._seq_num = random.randint(0, 0xffff)
def send_packet(self, request_object, timeout=None,
retries=None):
"""
Make send_packet thread safe
:param request_object:
:param timeout:
:param retries:
"""
if timeout is None:
timeout = self.timeout
if retries is None:
retries = self.retries
with Lock():
if self._seq_num >= 0xffff:
self._seq_num = 0
else:
self._seq_num += 1
return self._send_packet(
request_object, self._seq_num, addr=self.skarab_eth_ctrl_addr,
timeout=timeout, retries=retries, hostname=self.host
)
def _send_packet(self, request_object, sequence_number, addr,
timeout=sd.CONTROL_RESPONSE_TIMEOUT,
retries=sd.CONTROL_RESPONSE_RETRIES,
hostname='<unknown_host>'):
"""
Send payload via UDP packet to SKARAB
Sends request packets then waits for response packet if expected
Retransmits request packet if response not received
:param request_object: object containing the data to send to SKARAB
:param addr: hostname and port of SKARAB
:param timeout: how long to wait for a response before bailing
:param retries: how many times to retransmit a request
:return: response: returns response object or 'None' if no
response received.
"""
self._lock.acquire()
# create the payload and send it
request_payload = request_object.create_payload(sequence_number)
retransmit_count = 0
while retransmit_count < retries:
self.logger.debug('{}: retransmit attempts: {}, timeout = {}, retries = {}'.format(
hostname, retransmit_count, timeout, retries))
try:
self.logger.debug('{}: sending pkt({}, {}) to port {}.'.format(
hostname, request_object.packet['command_type'],
request_object.packet['seq_num'], addr))
self._skarab_control_sock.send(request_payload)
if not request_object.expect_response:
self.logger.debug(
'{}: no response expected for seq {}, '
'returning'.format(hostname, sequence_number))
self._lock.release()
return None
# get a required response
rx_packet = None
while rx_packet is None:
# here we want to receive a packet from the socket
# we pass the socket to the receive_packet function
rx_packet = self._receive_packet(
request_object, sequence_number, timeout, hostname)
self._lock.release()
return rx_packet
except SkarabResponseNotReceivedError:
# retransmit the packet
pass
except (KeyboardInterrupt, select.error):
self.logger.warning('{}: keyboard interrupt, clearing '
'buffer.'.format(hostname))
# wait to receive incoming responses
time.sleep(0.5)
try:
_ = self._skarab_control_sock.recvfrom(4096)
self.logger.info(
'{}: cleared recv buffer.'.format(hostname))
except socket.error:
self.logger.info(
'{}: buffer already empty'.format(hostname))
self._lock.release()
raise KeyboardInterrupt
retransmit_count += 1
self._lock.release()
errmsg = ('{}: retransmit count exceeded, giving up: {}, timeout = {}, retries = {}'.format(
hostname, retransmit_count, timeout, retries))
self.logger.debug(errmsg)
raise SkarabSendPacketError(errmsg)
def _receive_packet(self, request_object, sequence_number,
timeout, hostname):
"""
Receive a response to a packet.
:param request_object:
:param sequence_number:
:param timeout:
:param hostname:
:return: The response object, or None
"""
self.logger.debug('%s: reading response to sequence id %i.' % (
hostname, sequence_number))
try:
# wait for response until timeout
data_ready = select.select([self._skarab_control_sock], [], [], timeout)
# if we have a response, process it
if data_ready[0]:
data = self._skarab_control_sock.recvfrom(4096)
response_payload, address = data
self.logger.debug('%s: response from %s = %s' % (
hostname, str(address), repr(response_payload)))
# check if response is from the expected SKARAB
recvd_from_addr = address[0]
expected_recvd_from_addr = \
self._skarab_control_sock.getpeername()[0]
if recvd_from_addr != expected_recvd_from_addr:
self.logger.warning(
'%s: received response from %s, expected response from '
'%s. Discarding response.' % (
hostname, recvd_from_addr, expected_recvd_from_addr))
return None
# check the opcode of the response i.e. first two bytes
if response_payload[:2] == '\xff\xff':
self.logger.warning('%s: received unsupported opcode: 0xffff. '
'Discarding response.' % hostname)
return None
# check response packet size
if (len(response_payload)/2) != request_object.num_response_words:
self.logger.warning("%s: incorrect response packet size. "
"Discarding response" % hostname)
# self.logger.pdebug("Response packet not of correct size. "
self.logger.debug("Response packet not of correct size. "
"Expected %i words, got %i words.\n "
"Incorrect Response: %s" % (
request_object.num_response_words,
(len(response_payload)/2),
repr(response_payload)))
# self.logger.pdebug("%s: command ID - expected (%i) got (%i)" %
self.logger.debug("%s: command ID - expected (%i) got (%i)" %
(hostname, request_object.type + 1,
(struct.unpack('!H', response_payload[:2]))[0]))
# self.logger.pdebug("%s: sequence num - expected (%i) got (%i)" %
self.logger.debug("%s: sequence num - expected (%i) got (%i)" %
(hostname, sequence_number,
(struct.unpack('!H', response_payload[2:4]))[0]))
return None
# unpack the response before checking it
response_object = request_object.response.from_raw_data(
response_payload, request_object.num_response_words,
request_object.pad_words)
self.logger.debug('%s: response from %s, with seq num %i' % (
hostname, str(address),
response_object.seq_num))
expected_response_id = request_object.type + 1
if response_object.type != expected_response_id:
# Implementing a monkey patch here. On the MeerKAT site when the
# corr2_hardware_sensor_servlet and the corr2_servlet are running at the same
# time we periodically get the command ID warning below. It does not affect the
# operation of the telescope, but it does clutter the KCS logs. The
# corr2_servlet has two log handlers that propagate down to casperfpga. One
# handler sends data to a log file, while the other sends it to the console.
# The patch here is to disable the console logger for this one warning message.
# When this error occurs it will only be written to the log file, not to the
# console(which is collected by KCS). In this way we preserve the error without
# cluttering KCS.
# Find console handler and set to such a high level that the warning wont print.
handlers = self.logger.handlers
concoleLogHandlerDisabled = False
for handler in handlers:
if(issubclass(type(handler), CasperLogHandlers.CasperConsoleHandler)):
handler.setLevel(logging.CRITICAL)
consoleLogHandler = handler