aboutsummaryrefslogtreecommitdiffstats
path: root/firmware/usrp3/x300/x300_aurora_bist.py
blob: 962494b4674ff11068dea3500fe5d21bdab0b9d0 (plain)
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
#!/usr/bin/env python
#
# Copyright 2016 Ettus Research LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

from __future__ import print_function
from builtins import str
from builtins import range

import x300_debug
import argparse
import time
import datetime
import math
import tqdm
import numpy

########################################################################
# constants
########################################################################
SFP0_MAC_REG_BASE       = 0xC000
SFP1_MAC_REG_BASE       = 0xD000
SFP0_TYPE_REG_OFFSET    = 0xA000 + 16
SFP1_TYPE_REG_OFFSET    = 0xA000 + 20
SFP0_STATUS_REG_OFFSET  = 0xA000 + 32
SFP1_STATUS_REG_OFFSET  = 0xA000 + 36

SFP_TYPE_AURORA         = 2

MAC_REG_CTRL            = 0
MAC_REG_STATUS          = 0
MAC_REG_OVERRUNS        = 4
MAC_REG_CHECKSUM_ERRS   = 8
MAC_REG_BIST_SAMPS      = 12
MAC_REG_BIST_ERRORS     = 16

MAC_STATUS_LINK_UP_MSK          = 0x00000001
MAC_STATUS_HARD_ERR_MSK         = 0x00000002
MAC_STATUS_SOFT_ERR_MSK         = 0x00000004
MAC_STATUS_BIST_LOCKED_MSK      = 0x00000008
MAC_STATUS_BIST_LATENCY_MSK     = 0x000FFFF0
MAC_STATUS_BIST_LATENCY_OFFSET  = 4

MAC_CTRL_BIST_CHECKER_EN    = 0x00000001
MAC_CTRL_BIST_GEN_EN        = 0x00000002
MAC_CTRL_BIST_LOOPBACK_EN   = 0x00000004
MAC_CTRL_PHY_RESET          = 0x00000200
MAC_CTRL_BIST_RATE_MSK      = 0x000001F8
MAC_CTRL_BIST_RATE_OFFSET   = 3

BUS_CLK_RATE            = 187.50e6
BIST_MAX_TIME_LIMIT     = math.floor(pow(2,48)/BUS_CLK_RATE)-1

########################################################################
# utils
########################################################################
def get_aurora_info(ctrl):
    if (ctrl.peek(SFP0_TYPE_REG_OFFSET) == SFP_TYPE_AURORA):
        aur_port = 0
    elif (ctrl.peek(SFP1_TYPE_REG_OFFSET) == SFP_TYPE_AURORA):
        aur_port = 1
    else:
        aur_port = -1
    link_up = False
    if aur_port != -1:
        mac_base = SFP0_MAC_REG_BASE if aur_port == 0 else SFP1_MAC_REG_BASE
        link_up = ((ctrl.peek(mac_base + MAC_REG_STATUS) & MAC_STATUS_LINK_UP_MSK) != 0)
    return (aur_port, link_up)

def get_rate_setting(rate):
    RATE_RES_BITS = 6
    rate_sett = int((1.0*rate/(8e-6*BUS_CLK_RATE))*pow(2, RATE_RES_BITS))-1   # Round rate down
    rate_sett = numpy.clip(rate_sett, 0, pow(2, RATE_RES_BITS)-1)
    coerced_rate = ((1.0+rate_sett)/pow(2, RATE_RES_BITS))*(8e-6*BUS_CLK_RATE)
    return (rate_sett, coerced_rate)

def run_ber_loopback_bist(ctrls, duration, rate_tuple):
    (rate_sett, rate) = rate_tuple
    print('[INFO] Running BER Loopback BIST at %.0fMB/s for %.0fs...'%(rate,duration))
    # Determine offsets
    (mst_port, link_up) = get_aurora_info(ctrls['master'])
    MST_MAC_REG_BASE = SFP0_MAC_REG_BASE if (mst_port == 0) else SFP1_MAC_REG_BASE
    if 'slave' in ctrls:
        (sla_port, link_up) = get_aurora_info(ctrls['slave'])
        SLA_MAC_REG_BASE = SFP0_MAC_REG_BASE if (sla_port == 0) else SFP1_MAC_REG_BASE
    # Reset both PHYS
    ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_PHY_RESET)
    ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, 0)
    if 'slave' in ctrls:
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_PHY_RESET)
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, 0)
    time.sleep(1.5)
    # Put the slave in loopback mode and the master in BIST mode
    if 'slave' in ctrls:
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_LOOPBACK_EN)
    master_ctrl = (rate_sett<<MAC_CTRL_BIST_RATE_OFFSET)|MAC_CTRL_BIST_GEN_EN|MAC_CTRL_BIST_CHECKER_EN
    ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, master_ctrl)
    start_time = datetime.datetime.now()
    # Wait and check if BIST locked
    time.sleep(0.5)
    mst_status = ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_STATUS)
    if (not (mst_status & MAC_STATUS_BIST_LOCKED_MSK)):
        print('[ERROR] BIST engine did not lock onto a PRBS word!')
    # Wait for requested time
    try:
        for i in tqdm.tqdm(list(range(duration)), desc='[INFO] Progress'):
            time.sleep(1.0)
    except KeyboardInterrupt:
        print('[WARNING] Operation cancelled by user.')
    # Turn off the BIST generator and loopback
    ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_CHECKER_EN)
    stop_time = datetime.datetime.now()
    time.sleep(0.5)
    if 'slave' in ctrls:
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, 0)
    # Validate status and no overruns
    mst_status = ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_STATUS)
    mst_overruns = ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_OVERRUNS)
    if (mst_status & MAC_STATUS_HARD_ERR_MSK):
        print('[ERROR] Hard errors in master PHY')
    if (mst_overruns > 0):
        print('[ERROR] Buffer overruns in master PHY')
    if 'slave' in ctrls:
        sla_status = ctrls['slave'].peek(SLA_MAC_REG_BASE + MAC_REG_STATUS)
        sla_overruns = ctrls['slave'].peek(SLA_MAC_REG_BASE + MAC_REG_OVERRUNS)
        if (sla_status & MAC_STATUS_HARD_ERR_MSK):
            print('[ERROR] Hard errors in slave PHY')
        if (sla_overruns > 0):
            print('[ERROR] Buffer overruns in slave PHY')
    # Compure latency
    mst_samps = 65536.0*ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_BIST_SAMPS)
    mst_errors = 1.0*ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_BIST_ERRORS)
    if (mst_samps != 0):
        mst_latency_cyc = 16.0*((mst_status & MAC_STATUS_BIST_LATENCY_MSK) >> MAC_STATUS_BIST_LATENCY_OFFSET)
        time_diff = stop_time - start_time
        print('[INFO] BIST Complete!')
        print('- Elapsed Time              = ' + str(time_diff))
        print('- Max BER (Bit Error Ratio) = %.4g (%d errors out of %d)'%((mst_errors+1)/mst_samps,mst_errors,mst_samps))
        print('- Max Roundtrip Latency     = %.1fus'%(1e6*mst_latency_cyc/BUS_CLK_RATE))
        print('- Approx Throughput         = %.0fMB/s'%((8e-6*mst_samps)/time_diff.total_seconds()))
    else:
        print('[ERROR] BIST Failed!')
    # Drain and Cleanup
    print('[INFO] Cleaning up...')
    ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_CHECKER_EN)
    if 'slave' in ctrls:
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_CHECKER_EN)
    time.sleep(0.5)
    ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, 0)
    if 'slave' in ctrls:
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, 0)

def run_latency_loopback_bist(ctrls, duration, rate_tuple):
    (rate_sett, rate) = rate_tuple
    print('[INFO] Running Latency Loopback BIST at %.0fMB/s for %.0fs...'%(rate,duration))
    # Determine offsets
    (mst_port, link_up) = get_aurora_info(ctrls['master'])
    MST_MAC_REG_BASE = SFP0_MAC_REG_BASE if (mst_port == 0) else SFP1_MAC_REG_BASE
    if 'slave' in ctrls:
        (sla_port, link_up) = get_aurora_info(ctrls['slave'])
        SLA_MAC_REG_BASE = SFP0_MAC_REG_BASE if (sla_port == 0) else SFP1_MAC_REG_BASE
    # Reset both PHYS
    ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_PHY_RESET)
    ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, 0)
    if 'slave' in ctrls:
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_PHY_RESET)
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, 0)
    time.sleep(1.5)

    # Put the slave in loopback mode and the master in BIST mode
    if 'slave' in ctrls:
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_LOOPBACK_EN)
    master_ctrl = (rate_sett<<MAC_CTRL_BIST_RATE_OFFSET)|MAC_CTRL_BIST_GEN_EN|MAC_CTRL_BIST_CHECKER_EN

    start_time = datetime.datetime.now()
    latencies = []
    mst_lock_errors = 0
    mst_hard_errors = 0
    mst_overruns = 0
    try:
        for i in tqdm.tqdm(list(range(duration*10)), desc='[INFO] Progress'):
            ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, master_ctrl)
            # Wait and check if BIST locked
            time.sleep(0.05)
            mst_status = ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_STATUS)
            if (not (mst_status & MAC_STATUS_BIST_LOCKED_MSK)):
                mst_lock_errors += 1
            # Turn off the BIST generator
            ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_CHECKER_EN)
            # Validate status and no overruns
            mst_status = ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_STATUS)
            mst_overruns += ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_OVERRUNS)
            if (mst_status & MAC_STATUS_HARD_ERR_MSK):
                mst_hard_errors += 1
            time.sleep(0.05)
            ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, 0)
            # Compure latency
            mst_latency_cyc = 16.0*((mst_status & MAC_STATUS_BIST_LATENCY_MSK) >> MAC_STATUS_BIST_LATENCY_OFFSET)
            mst_latency_us = 1e6*mst_latency_cyc/BUS_CLK_RATE
            latencies.append(mst_latency_us)
    except KeyboardInterrupt:
        print('[WARNING] Operation cancelled by user.')
    stop_time = datetime.datetime.now()
    # Report
    if (mst_lock_errors > 0):
        print('[ERROR] BIST engine did not lock onto a PRBS word %d times!'%(mst_lock_errors))
    if (mst_hard_errors > 0):
        print('[ERROR] There were %d hard errors in master PHY'%(mst_hard_errors))
    if (mst_overruns > 0):
        print('[ERROR] There were %d buffer overruns in master PHY'%(mst_overruns))

    print('[INFO] BIST Complete!')
    print('- Elapsed Time               = ' + str(stop_time - start_time))
    print('- Roundtrip Latency Mean     = %.2fus'%(numpy.mean(latencies)))
    print('- Roundtrip Latency Stdev    = %.2fus'%(numpy.std(latencies)))
    # Turn off BIST loopback
    time.sleep(0.5)
    if 'slave' in ctrls:
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, 0)
    if 'slave' in ctrls:
        sla_status = ctrls['slave'].peek(SLA_MAC_REG_BASE + MAC_REG_STATUS)
        sla_overruns = ctrls['slave'].peek(SLA_MAC_REG_BASE + MAC_REG_OVERRUNS)
        if (sla_status & MAC_STATUS_HARD_ERR_MSK):
            print('[ERROR] Hard errors in slave PHY')
        if (sla_overruns > 0):
            print('[ERROR] Buffer overruns in slave PHY')
    # Drain and Cleanup
    print('[INFO] Cleaning up...')
    ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_CHECKER_EN)
    if 'slave' in ctrls:
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_CHECKER_EN)
    time.sleep(0.5)
    ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, 0)
    if 'slave' in ctrls:
        ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, 0)

########################################################################
# command line options
########################################################################
def get_options():
    parser = argparse.ArgumentParser(description='Controller for the USRP X3X0 Aurora BIST Engine')
    parser.add_argument('--master', type=str, default=None, required=True, help='IP Address of master USRP-X3X0 device')
    parser.add_argument('--slave', type=str, default=None, help='IP Address of slave USRP-X3X0 device')
    parser.add_argument('--test', type=str, default='ber', choices=['ber', 'latency'], help='Type of test to run')
    parser.add_argument('--duration', type=int, default=10, help='Duration of test in seconds')
    parser.add_argument('--rate', type=int, default=1245, help='BIST throughput in MB/s')
    return parser.parse_args()

########################################################################
# main
########################################################################
if __name__=='__main__':
    options = get_options()

    if (options.duration < 0 or options.duration > BIST_MAX_TIME_LIMIT):
        raise Exception('Invalid duration. Min = 0s and Max = %ds'%(BIST_MAX_TIME_LIMIT))

    ctrls = dict()
    ctrls['master'] = x300_debug.ctrl_socket(addr=options.master)
    if options.slave:
        ctrls['slave'] = x300_debug.ctrl_socket(addr=options.slave)

    # Report device and core info
    links_up = True
    for node in ctrls:
        print('[INFO] ' + node.upper() + ':')
        ctrl = ctrls[node]
        (aur_port, link_up) = get_aurora_info(ctrl)
        if aur_port >= 0:
            status_str = str(aur_port) + (' UP' if link_up else ' DOWN')
        else:
            status_str = 'Not Detected!'
        print('- Mgmt IP Addr  : ' + (options.master if node == 'master' else options.slave))
        print('- Aurora Status : Port ' + status_str)
        links_up = links_up & link_up

    # Sanity check
    if not links_up:
        print('[ERROR] At least one of the links is down. Cannot proceed.')
        exit(1)

    # Run BIST
    if options.test == 'ber':
        run_ber_loopback_bist(ctrls, options.duration, get_rate_setting(options.rate))
    else:
        run_latency_loopback_bist(ctrls, options.duration, get_rate_setting(options.rate))