"""TROPIC01 Chip ID parsing module
This module provides classes and constants for parsing and representing
the TROPIC01 chip identification structure.
Main exports:
- ChipId: Main chip ID structure parser
- SerialNumber: Serial number structure parser
- Constants available from tropicsquare.chip_id.constants
Example:
>>> from tropicsquare.chip_id import ChipId
>>> chip_id = ChipId(raw_chip_id_bytes)
>>> print(chip_id.package_type_name)
'QFN32'
>>> print(chip_id.fab_name)
'EPS Brno'
>>> print(chip_id.serial_number)
SN:0x42 Fab:0x001 PN:0x123 Wafer:0x05 Coords:(128,256)
"""
from tropicsquare.chip_id.serial_number import SerialNumber
from tropicsquare.chip_id.constants import (
CHIP_ID_SIZE,
PACKAGE_TYPES,
FAB_LOCATIONS
)
[docs]
class ChipId:
"""TROPC01 Chip ID structure parser
This class parses the 128-byte chip identification structure that contains
comprehensive information about the chip including manufacturing data,
provisioning information, firmware versions, and serial number.
The structure follows the lt_chip_id_t layout from libtropic with all
fields properly parsed and exposed as attributes.
**Structure layout (128 bytes total):**
- Bytes 0-3: Chip ID version (4 bytes)
- Bytes 4-19: Factory level chip info (16 bytes)
- Bytes 20-27: Functional test info (8 bytes)
- Bytes 28-31: Silicon revision (4 bytes, ASCII)
- Bytes 32-33: Package type ID (2 bytes, big-endian)
- Bytes 34-35: Reserved field 1 (2 bytes)
- Bytes 36-39: Provisioning info - version/fab/part number (4 bytes, big-endian)
- Bytes 40-41: Provisioning date (2 bytes, big-endian)
- Bytes 42-45: HSM version (4 bytes)
- Bytes 46-49: Program version (4 bytes)
- Bytes 50-51: Reserved field 2 (2 bytes)
- Bytes 52-67: Serial number structure (16 bytes)
- Bytes 68-83: Part number data (16 bytes)
- Bytes 84-85: Provisioning template version (2 bytes, big-endian)
- Bytes 86-89: Provisioning template tag (4 bytes)
- Bytes 90-91: Provisioning specification version (2 bytes, big-endian)
- Bytes 92-95: Provisioning specification tag (4 bytes)
- Bytes 96-100: Batch ID (5 bytes)
- Bytes 101-103: Reserved field 3 (3 bytes)
- Bytes 104-127: Reserved field 4 / Padding (24 bytes)
Attributes:
raw (bytes): Original raw chip ID data (128 bytes)
chip_id_version (tuple): Chip ID version as 4-element tuple
fl_chip_info (bytes): Factory level chip information (16 bytes)
func_test_info (bytes): Functional test information (8 bytes)
silicon_rev (str): Silicon revision (ASCII string, null-terminated)
package_type_id (int): Package type identifier
package_type_name (str): Human-readable package type name
provisioning_version (int): Provisioning version number (8-bit)
fab_id (int): Fabrication facility ID (12-bit)
fab_name (str): Human-readable fabrication facility name
part_number_id (int): Part number identifier (12-bit)
provisioning_date (int): Provisioning date value
hsm_version (tuple): HSM version as 4-element tuple
prog_version (tuple): Program version as 4-element tuple
serial_number (SerialNumber): Parsed serial number structure
part_num_data (bytes): Part number data (16 bytes)
prov_template_version (int): Provisioning template version
prov_template_tag (bytes): Provisioning template tag (4 bytes)
prov_spec_version (int): Provisioning specification version
prov_spec_tag (bytes): Provisioning specification tag (4 bytes)
batch_id (bytes): Batch identifier (5 bytes)
"""
[docs]
def __init__(self, data: bytes):
"""Parse chip ID from raw bytes
:param data: Raw chip ID bytes (must be exactly 128 bytes)
:raises ValueError: If data length is not 128 bytes
"""
if len(data) != CHIP_ID_SIZE:
raise ValueError(f"Chip ID must be {CHIP_ID_SIZE} bytes, got {len(data)}")
self.raw = data
# Bytes 0-3: Chip ID version (4 bytes)
self.chip_id_version = tuple(data[0:4])
# Bytes 4-19: Factory level chip info (16 bytes)
self.fl_chip_info = data[4:20]
# Bytes 20-27: Functional test info (8 bytes)
self.func_test_info = data[20:28]
# Bytes 28-31: Silicon revision (4 bytes, ASCII)
self.silicon_rev = data[28:32].decode('ascii', 'ignore').rstrip('\x00')
# Bytes 32-33: Package type ID (2 bytes, big-endian)
self.package_type_id = int.from_bytes(data[32:34], "big")
self.package_type_name = PACKAGE_TYPES.get(self.package_type_id, "Unknown")
# Bytes 34-35: Reserved field 1 (skipped)
# Bytes 36-39: Provisioning info (4 bytes, big-endian)
# Layout: [prov_ver:8][fab_id:12][part_num:12] = 32 bits
prov_data = int.from_bytes(data[36:40], "big")
self.provisioning_version = (prov_data >> 24) & 0xFF # Bits 24-31
self.fab_id = (prov_data >> 12) & 0xFFF # Bits 12-23
self.part_number_id = prov_data & 0xFFF # Bits 0-11
self.fab_name = FAB_LOCATIONS.get(self.fab_id, "Unknown")
# Bytes 40-41: Provisioning date (2 bytes, big-endian)
self.provisioning_date = int.from_bytes(data[40:42], "big")
# Bytes 42-45: HSM version (4 bytes)
self.hsm_version = tuple(data[42:46])
# Bytes 46-49: Program version (4 bytes)
self.prog_version = tuple(data[46:50])
# Bytes 50-51: Reserved field 2 (skipped)
# Bytes 52-67: Serial number (16 bytes)
self.serial_number = SerialNumber(data[52:68])
# Bytes 68-83: Part number data (16 bytes)
self.part_num_data = data[68:84]
# Bytes 84-85: Provisioning template version (2 bytes, big-endian)
self.prov_template_version = int.from_bytes(data[84:86], "big")
# Bytes 86-89: Provisioning template tag (4 bytes)
self.prov_template_tag = data[86:90]
# Bytes 90-91: Provisioning specification version (2 bytes, big-endian)
self.prov_spec_version = int.from_bytes(data[90:92], "big")
# Bytes 92-95: Provisioning specification tag (4 bytes)
self.prov_spec_tag = data[92:96]
# Bytes 96-100: Batch ID (5 bytes)
self.batch_id = data[96:101]
# Bytes 101-103: Reserved field 3 (skipped)
# Bytes 104-127: Reserved field 4 / Padding (skipped)
[docs]
def __str__(self) -> str:
"""Get human-readable multi-line string representation
:returns: Multi-line formatted string with key chip ID information
"""
lines = [
"TROPIC01 Chip ID:",
f" Chip ID Version: {'.'.join(map(str, self.chip_id_version))}",
f" Silicon Revision: {self.silicon_rev}",
f" Package Type: {self.package_type_name} (0x{self.package_type_id:04X})",
f" Fabrication: {self.fab_name} (0x{self.fab_id:03X})",
f" Part Number ID: 0x{self.part_number_id:03X}",
f" HSM Version: {'.'.join(map(str, self.hsm_version))}",
f" Program Version: {'.'.join(map(str, self.prog_version))}",
f" Serial Number: {self.serial_number}",
f" Batch ID: {self.batch_id.hex()}",
]
return '\n'.join(lines)
[docs]
def __repr__(self) -> str:
"""Get detailed string representation for debugging
:returns: Detailed representation with class name
"""
return f"ChipId(package={self.package_type_name}, fab={self.fab_name}, sn={self.serial_number.sn:02X})"
[docs]
def to_dict(self) -> dict:
"""Convert chip ID to dictionary representation
:returns: Dictionary containing all chip ID fields with nested serial number
"""
return {
'chip_id_version': list(self.chip_id_version),
'fl_chip_info': self.fl_chip_info.hex(),
'func_test_info': self.func_test_info.hex(),
'silicon_rev': self.silicon_rev,
'package_type_id': self.package_type_id,
'package_type_name': self.package_type_name,
'provisioning_version': self.provisioning_version,
'fab_id': self.fab_id,
'fab_name': self.fab_name,
'part_number_id': self.part_number_id,
'provisioning_date': self.provisioning_date,
'hsm_version': list(self.hsm_version),
'prog_version': list(self.prog_version),
'serial_number': self.serial_number.to_dict(),
'part_num_data': self.part_num_data.hex(),
'prov_template_version': self.prov_template_version,
'prov_template_tag': self.prov_template_tag.hex(),
'prov_spec_version': self.prov_spec_version,
'prov_spec_tag': self.prov_spec_tag.hex(),
'batch_id': self.batch_id.hex(),
}