| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # |
| # Copyright (C) 2017-2020 The Project X-Ray Authors. |
| # |
| # Use of this source code is governed by a ISC-style |
| # license that can be found in the LICENSE file or at |
| # https://opensource.org/licenses/ISC |
| # |
| # SPDX-License-Identifier: ISC |
| import argparse |
| |
| REGISTER_LAYOUT = { |
| 'CLKOUT1': [ |
| ('LOW_TIME', 6), |
| ('HIGH_TIME', 6), |
| ('OUTPUT_ENABLE', 1), |
| ('PHASE_MUX', 3), |
| ], |
| 'CLKOUT2': [ |
| ('DELAY_TIME', 6), |
| ('NO_COUNT', 1), |
| ('EDGE', 1), |
| ('MX', 2), |
| ('FRAC_WF_R', 1), |
| ('FRAC_EN', 1), |
| ('FRAC', 3), |
| ('RESERVED', 1), |
| ], |
| 'CLKOUT2_FRACTIONAL': [ |
| ('DELAY_TIME', 6), |
| ('NO_COUNT', 1), |
| ('EDGE', 1), |
| ('MX', 2), |
| ('FRAC_WF_F', 1), |
| ('PHASE_MUX_F', 3), |
| ('RESERVED', 2), |
| ], |
| 'DIVCLK': [ |
| ('LOW_TIME', 6), |
| ('HIGH_TIME', 6), |
| ('NO_COUNT', 1), |
| ('EDGE', 1), |
| ('RESERVED', 2), |
| ], |
| 'LOCKREG1': [ |
| ('LKTABLE', 10, 20), |
| ('LOCKREG1_RESERVED', 6, 0), |
| ], |
| 'LOCKREG2': [ |
| ('LKTABLE', 10, 0), |
| ('LKTABLE', 5, 30), |
| ('LOCKREG2_RESERVED', 1, 0), |
| ], |
| 'LOCKREG3': [ |
| ('LKTABLE', 10, 10), |
| ('LKTABLE', 5, 35), |
| ('LOCKREG3_RESERVED', 1, 0), |
| ], |
| 'FILTREG1': [ |
| ('FILTREG1_RESERVED', 8, 0), |
| ('TABLE', 1, 6), |
| ('FILTREG1_RESERVED', 2, 8), |
| ('TABLE', 2, 7), |
| ('FILTREG1_RESERVED', 2, 10), |
| ('TABLE', 1, 9), |
| ], |
| 'FILTREG2': [ |
| ('FILTREG2_RESERVED', 4, 0), |
| ('TABLE', 1, 0), |
| ('FILTREG2_RESERVED', 2, 4), |
| ('TABLE', 2, 1), |
| ('FILTREG2_RESERVED', 2, 6), |
| ('TABLE', 2, 3), |
| ('FILTREG2_RESERVED', 2, 8), |
| ('TABLE', 1, 5), |
| ], |
| 'POWER_REG': [ |
| ('POWER_REG', 16), |
| ], |
| } |
| |
| BASE_OFFSET = 0x00 |
| REGISTER_MAP = [] |
| |
| REGISTER_MAP.append(None) |
| REGISTER_MAP.append(None) |
| |
| for idx in range(3): |
| REGISTER_MAP.append(None) |
| |
| REGISTER_MAP.append(None) |
| |
| # 0x06 - 0x15 |
| for output in ['CLKOUT5', 'CLKOUT0', 'CLKOUT1', 'CLKOUT2', 'CLKOUT3', |
| 'CLKOUT4', 'CLKOUT6', 'CLKFBOUT']: |
| if output is not None: |
| REGISTER_MAP.append(('CLKOUT1', output)) |
| if output in ['CLKOUT5', 'CLKOUT6']: |
| # CLKOUT5 CLKOUT2 register actually controls the fractional of |
| # CLKOUT0. |
| # CLKOUT6 CLKOUT2 register actually controls the fractional of |
| # CLKFBOUT. |
| REGISTER_MAP.append(('CLKOUT2_FRACTIONAL', output)) |
| else: |
| REGISTER_MAP.append(('CLKOUT2', output)) |
| else: |
| REGISTER_MAP.append(None) |
| REGISTER_MAP.append(None) |
| |
| # 0x16 |
| REGISTER_MAP.append(('DIVCLK', 'DIVCLK')) |
| # 0x17 |
| REGISTER_MAP.append(None) |
| # 0x18-0x1A |
| REGISTER_MAP.append(('LOCKREG1', 'LOCKREG1')) |
| REGISTER_MAP.append(('LOCKREG2', 'LOCKREG2')) |
| REGISTER_MAP.append(('LOCKREG3', 'LOCKREG3')) |
| |
| for _ in range(0x28 - 0x1A - 1): |
| REGISTER_MAP.append(None) |
| |
| REGISTER_MAP.append(('POWER_REG', 'POWER_REG')) |
| |
| for _ in range(0x4E - 0x28 - 1): |
| REGISTER_MAP.append(None) |
| |
| # 0x4E - 0x4F |
| REGISTER_MAP.append(('FILTREG1', 'FILTREG1')) |
| REGISTER_MAP.append(('FILTREG2', 'FILTREG2')) |
| |
| for _ in range(0x20): |
| REGISTER_MAP.append(None) |
| |
| |
| class RegisterAddress(object): |
| def __init__(self, frame_offsets, bit_offset, reverse=False): |
| self.frame_index = 0 |
| self.frame_offsets = frame_offsets |
| self.bit_offset = bit_offset |
| self.bits_used = set() |
| self.reverse = reverse |
| |
| def next_bit(self, used=True): |
| output = '{}_{}'.format( |
| self.frame_offsets[self.frame_index], self.bit_offset) |
| |
| if used: |
| self.bits_used.add(output) |
| |
| self.frame_index += 1 |
| if self.frame_index >= len(self.frame_offsets): |
| self.frame_index = 0 |
| if self.reverse: |
| self.bit_offset -= 1 |
| else: |
| self.bit_offset += 1 |
| |
| return output |
| |
| |
| def passthrough_non_register_segbits(seg_in): |
| """ Filter input segbits file and capture register base offset. |
| |
| Some MMCM bit ranges are documented registers in the PLL/MMCM dynamic |
| reconfiguration iterface. These features will be generated in |
| output_registers. In order for output_registers to function, it needs |
| the starting bit offset of the register space, which is based off of |
| base_offset_register segbit definition. |
| |
| Other features generated in fuzzing are passed through. |
| |
| """ |
| base_offset_register = 'CMT_LOWER_B.MMCME2_ADV.CLKOUT5_DIVIDE[1]' |
| |
| bit_offset = None |
| in_use = None |
| with open(seg_in, 'r') as f: |
| for l in f: |
| if l.startswith(base_offset_register): |
| parts = l.split() |
| assert len(parts) == 2 |
| assert parts[0] == base_offset_register |
| frame_offset, bit_index = map(int, parts[1].split('_')) |
| |
| assert frame_offset == 29 |
| assert bit_index > 3 |
| bit_offset = bit_index + 3 + 3 * 16 |
| |
| continue |
| |
| if 'IN_USE' in l: |
| assert in_use is None |
| in_use = l.strip() |
| continue |
| |
| parts = l.split() |
| feature_parts = parts[0].split('.') |
| |
| if len(feature_parts) < 3: |
| print(l.strip()) |
| continue |
| |
| if feature_parts[2] == 'BANDWIDTH': |
| continue |
| |
| if '[' not in feature_parts[2]: |
| print(l.strip()) |
| continue |
| |
| base_feature = feature_parts[2].split('[') |
| |
| if base_feature[0] in [ |
| 'CLKOUT0_DIVIDE_F', |
| 'CLKOUT1_DIVIDE', |
| 'CLKOUT2_DIVIDE', |
| 'CLKOUT3_DIVIDE', |
| 'CLKOUT4_DIVIDE', |
| 'CLKOUT5_DIVIDE', |
| 'CLKOUT6_DIVIDE', |
| 'DIVCLK_DIVIDE', |
| 'CLKFBOUT_MULT_F', |
| 'CLKOUT0_DUTY_CYCLE', |
| ]: |
| # These features are MMCM registers, so ignore the base |
| continue |
| |
| print(l.strip()) |
| |
| assert bit_offset is not None |
| assert in_use is not None |
| return bit_offset, in_use |
| |
| |
| def output_registers(bit_offset, in_use): |
| """ Output segbits for the known MMCM register space. |
| |
| The first bit offset in the register space is required to generate this |
| output. |
| |
| """ |
| reg = RegisterAddress( |
| frame_offsets=[29, 28], bit_offset=bit_offset, reverse=True) |
| |
| for idx, register in enumerate(REGISTER_MAP): |
| if register is None: |
| for _ in range(16): |
| reg.next_bit(used=False) |
| continue |
| |
| layout, register_name = register |
| |
| layout_bits = REGISTER_LAYOUT[layout] |
| |
| simple_layout = len(layout_bits[0]) == 2 |
| |
| if True: |
| bit_count = 0 |
| if simple_layout: |
| for field, width in layout_bits: |
| for bit in range(width): |
| bit_count += 1 |
| |
| if field is None: |
| reg.next_bit(used=False) |
| continue |
| |
| print( |
| 'CMT_LOWER_B.MMCME2_ADV.{}_{}_{}[{}] {}'.format( |
| register_name, layout, field, bit, |
| reg.next_bit())) |
| else: |
| for field, width, start_bit in layout_bits: |
| for bit in range(width): |
| bit_count += 1 |
| |
| if field is None: |
| reg.next_bit(used=False) |
| continue |
| |
| print( |
| 'CMT_LOWER_B.MMCME2_ADV.{}[{}] {}'.format( |
| field, start_bit + bit, reg.next_bit())) |
| |
| assert bit_count == 16 |
| else: |
| for bit in range(16): |
| if register_name != layout or layout in ['CLKOUT1', 'CLKOUT2']: |
| print( |
| 'CMT_LOWER_B.MMCME2_ADV.{}_{}[{}] {}'.format( |
| register_name, layout, bit, reg.next_bit())) |
| else: |
| print( |
| 'CMT_LOWER_B.MMCME2_ADV.{}[{}] {}'.format( |
| register_name, bit, reg.next_bit())) |
| |
| parts = in_use.split() |
| feature = parts[0] |
| bits = [p for p in parts[1:] if p not in reg.bits_used] |
| print('{} {}'.format(feature, ' '.join(bits))) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(description="") |
| |
| parser.add_argument('--seg_in') |
| |
| args = parser.parse_args() |
| |
| bit_offset, in_use = passthrough_non_register_segbits(args.seg_in) |
| |
| output_registers(bit_offset, in_use) |
| |
| |
| if __name__ == "__main__": |
| main() |