diff --git a/tools/timing/README.md b/tools/timing/README.md index 82f2d9f77..73bb747f6 100644 --- a/tools/timing/README.md +++ b/tools/timing/README.md @@ -1,3 +1,48 @@ -# Timing +# I3C Timing Generator & Validator -TBD +This module calculates and validates I3C and I2C Fast Mode timing parameters (in system clock cycles) for an I3C Controller based on the MIPI I3C Basic specification. + +## Core Functions + +* **`generate_timings(f_scl, f_sys, duty_cycle=0.5)`**: Calculates required CSR register cycle counts based on target SCL and system clock frequencies (in Hz). Clamps max SCL frequency to 12.9 MHz and automatically enforces physical hardware minimums. +* **`validate_timings(timings, f_sys)`**: Validates a dictionary of timing cycles against absolute I3C nanosecond constraints, logging errors for any spec violations. +* **`log_timing_configuration(timings, f_sys=None)`**: Neatly formats and logs the generated timing configuration into a table. If `f_sys` is provided, it also calculates and displays the physical time in nanoseconds for each register. + +## Quick Start + +```python + +import logging +from timings import generate_timings, log_timing_configuration + +logging.basicConfig(level=logging.INFO) + +# Generate valid CSR timings for 12.5 MHz SCL and 333.33 MHz System Clock +SYS_CLK = 333.333e6 +csr_timings = generate_timings(f_scl=12.5e6, f_sys=SYS_CLK) + +# Print the nicely formatted table of cycle counts and physical times +log_timing_configuration(csr_timings, f_sys=SYS_CLK) + +``` + +## Usage + +The `timing.py` script can be invoked to display register values corresponding to the clock settings provided in the command arguments: + +```python +usage: timings.py [-h] [--freq FREQ] [--bus_freq BUS_FREQ] [--duty_cycle DUTY_CYCLE] [--md] [--target_name TARGET_NAME] + +Generate and validate I3C timings. + +options: + -h, --help show this help message and exit + --freq FREQ System clock frequency in Hz (Default: 200.0e6) + --bus_freq BUS_FREQ Target I3C bus frequency in Hz (Default: 12.5e6) + --duty_cycle DUTY_CYCLE + Target duty cycle (Default: 0.5) + --md Output the results as a MyST Markdown table (suppresses standard logging) + --target_name TARGET_NAME + Name of the configuration for the Markdown header (e.g., 'FPGA', 'ASIC') + +``` diff --git a/tools/timing/specification.py b/tools/timing/specification.py deleted file mode 100644 index dd981e2a6..000000000 --- a/tools/timing/specification.py +++ /dev/null @@ -1,72 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -from enum import Enum - - -class IXCModes(Enum): - LEGACY_400k = 1 - LEGACY_1M = 2 - I3C_SDR_12M5_OD = 3 - I3C_SDR_12M5_PP = 4 - - -MODE_FREQ_DICT = { - IXCModes.LEGACY_400k: 400e3, - IXCModes.LEGACY_1M: 1e6, - IXCModes.I3C_SDR_12M5_OD: 12.5e6, - IXCModes.I3C_SDR_12M5_PP: 12.5e6, -} - -# Table 85 Timing with I2C Legacy Devices -SPECIFICATIONS = { - IXCModes.LEGACY_400k: { - "f_scl_max": 400e3, - "t_su_sta_min": 600e-9, - "t_hd_sta_min": 600e-9, - "t_low_min": 1300e-9, - "t_dig_l_min": "t_low+t_r_cl", - "t_high_min": 600e-9, - "t_dig_h_min": "t_high-t_r_cl", - "t_su_dat_min": 100e-9, - "t_hd_dat_min": None, - "t_r_cl_min": 20e-9, - "t_f_cl_min": "20*(Vdd/5.5V)", - "t_r_da_min": 20e-9, - "t_r_da_od_min": 20e-9, - "t_f_da_min": "20*(Vdd/5.5V)", - "t_su_sto_min": 600e-9, - "t_buf_min": 1.3e-6, - "t_spike_min": 0, - }, - IXCModes.LEGACY_1M: { - "f_scl_max": 1e6, - "t_su_sta_min": 260e-9, - "t_hd_sta_min": 260e-9, - "t_low_min": 500e-9, - "t_dig_l_min": "t_low+t_r_cl", - "t_high_min": 260e-9, - "t_dig_h_min": "t_high-t_r_cl", - "t_su_dat_min": 50e-9, - "t_hd_dat_min": None, - "t_r_cl_min": None, - "t_f_cl_min": "20*(Vdd/5.5V)", - "t_r_da_min": None, - "t_r_da_od_min": None, - "t_f_da_min": "20*(Vdd/5.5V)", - "t_su_sto_min": 260e-9, - "t_buf_min": 0.5e-6, - "t_spike_min": 0, - }, -} - - -class IXCSpecification: - def __init__(self, mode): - self.mode = mode - self.mode_freq = self.get_mode_freq(mode) - self.spec = self.get_spec(mode) - - def get_mode_freq(self, mode): - return MODE_FREQ_DICT[mode] - - def get_spec(self, mode): - return SPECIFICATIONS[mode] diff --git a/tools/timing/timing.py b/tools/timing/timing.py deleted file mode 100644 index a220f9057..000000000 --- a/tools/timing/timing.py +++ /dev/null @@ -1,293 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 - -import logging -import math - -from engineering_notation import EngNumber as EN -from specification import MODE_FREQ_DICT, IXCModes, IXCSpecification -from utils import cycles2seconds, f2halfT, f2T, norm_ceil, setup_logger - - -def get_firmware_settings(spec, sys_clk=100e6): - sys_period = f2T(sys_clk) - logging.debug(f"sys_period \t= {EN(sys_period)}") - - THIGH_MIN = max(norm_ceil(spec.spec["t_high_min"], sys_period), 4) - TLOW_MIN = norm_ceil(spec.spec["t_low_min"], sys_period) - THD_STA_MIN = norm_ceil(spec.spec["t_hd_sta_min"], sys_period) - TSU_STA_MIN = norm_ceil(spec.spec["t_su_sta_min"], sys_period) - TSU_DAT_MIN = norm_ceil(spec.spec["t_su_dat_min"], sys_period) - T_BUF_MIN = norm_ceil(spec.spec["t_buf_min"], sys_period) - T_STO_MIN = norm_ceil(spec.spec["t_su_sto_min"], sys_period) - - settings = { - "THIGH_MIN": THIGH_MIN, - "TLOW_MIN": TLOW_MIN, - "THD_STA_MIN": THD_STA_MIN, - "TSU_STA_MIN": TSU_STA_MIN, - "TSU_DAT_MIN": TSU_DAT_MIN, - "T_BUF_MIN": T_BUF_MIN, - "T_STO_MIN": T_STO_MIN, - } - - # Rise/fall time - # All rf in spec were the same, so simplifying - if spec.mode == IXCModes.LEGACY_1M: - T_R = T_F = norm_ceil(20e-9, sys_period) - logging.debug("Arbitrarily set T_R and T_F to 20ns") - logging.debug("Specification does not constrain it.") - logging.debug("C.f. Table 85 Legacy Mody 1MHz/Fm+, entry: t_r_cl") - else: - T_R = T_F = norm_ceil(spec.spec["t_r_cl_min"], sys_period) - - logging.debug("Rise/fall times are not fw controlled. They depend on electrical design.") - logging.debug(f"T_R = {EN(T_R)}") - logging.debug(f"T_F = {EN(T_F)}") - settings["T_R"] = T_R - settings["T_F"] = T_F - - t_scl_min = f2T(spec.spec["f_scl_max"]) - logging.debug(f"t_scl_min = {EN(t_scl_min)}") - - MIN_PERIOD = math.ceil(t_scl_min / sys_period) - logging.debug(f"MIN_PERIOD = {MIN_PERIOD}") - - # Assuming THIGH=TLOW - # 2*THIGH >= PERIOD - T_F - T_R - T_HIGH = math.ceil(max((MIN_PERIOD - T_F - T_R) / 2, spec.spec["t_high_min"] / sys_period)) - T_LOW = T_HIGH - logging.debug(f"T_HIGH = {T_HIGH}") - logging.debug(f"T_LOW = {T_LOW}") - - settings["MIN_PERIOD"] = MIN_PERIOD - settings["T_HIGH"] = T_HIGH - settings["T_LOW"] = T_LOW - logging.info(f"Settings={settings}") - - return settings - - -def log_generic_timings(mode, sys_freq): - freq = MODE_FREQ_DICT[mode] - t_high = f2halfT(freq) - t_low = f2halfT(freq) - oversampling = sys_freq / freq - counter_high_low = 0.5 * oversampling - - logging.info(f"freq = {EN(freq)}") - logging.info(f"t_high = {EN(t_high)}") - logging.info(f"t_low = {EN(t_low)}") - logging.info(f"oversampling = {EN(oversampling)}") - logging.info(f"counter_high_low = {EN(counter_high_low)}") - - -def get_i3c_firmware_settings(): - freq = 1e9 - width_timing_csr(freq) - timing_registers_reset() - bus_condition_timing_register(freq) - - -# TODO: From discrete settings calculate physical values on bus to validate them (min,max) -def firmware_to_timings(): - pass - - -# Fig 144 I3C Start Timing -# Timing t_ds_od on figure 144 is not defined in text! -def get_i3c_start_timings(): - start_timings = { - "tSDA_LOW": 0, - "tSCL_LOW": 0, # t_fda_od+t_cas - "tSDA_RELEASE": 0, # t_cf + t_ds_od # t_ds_od is NOT DEFINED! - "tSCL_RELEASE": 0, # t_low_od + t_cr + t_cf - } - - spec = { - "f_scl_max": 12.5e6, - "t_f_da_od_max": 12e-9, - "t_cf": 12e-9, - "t_cr": 12e-9, - "t_ds_od": 1e-9, # NOT DEFINED - "t_cas_min": 38.4e-9, - "t_low_od_min": 200e-9, - } - tSCL_LOW = spec["t_f_da_od_max"] + spec["t_cas_min"] - tSDA_RELEASE = spec["t_cf"] + spec["t_ds_od"] - tSCL_RELEASE = spec["t_f_da_od_max"] + spec["t_cr"] + spec["t_cf"] - - start_timings["tSCL_LOW"] = tSCL_LOW - start_timings["tSDA_RELEASE"] = tSDA_RELEASE - start_timings["tSCL_RELEASE"] = tSCL_RELEASE - - logging.info(start_timings) - return start_timings - - -# TODO: Finish i3c timings -def get_i3c_sdr_timings(spec, sys_clk=100e6): - spec = { - "f_scl_max": 12.5e6, - "t_cr": 12e-9, - } - bus_period = f2T(spec["f_scl_max"]) - # duty_cycle = 0.5 - # t_dig_h = duty_cycle * bus_period - # t_dig_l = (1 - duty_cycle) * bus_period - - sdr_timings = { - "tCLK_PULSE": 0, # t_cr + t_high - # From clock low to start - # or from clock high to low - } - - tSAMPLE_SDA = spec["t_cr"] - tCLK_PULSE = bus_period - tSAMPLE_SDA - - sdr_timings["tCLK_PULSE"] = tCLK_PULSE - sdr_timings["tSAMPLE_SDA"] = tSAMPLE_SDA - - logging.info(f"Settings={sdr_timings}") - return sdr_timings - - -def get_i3c_rise_fall_timings(bus_period): - tcr = 150e6 * bus_period * 1e-9 - logging.info(f"Worst case rise/fall time = {EN(tcr)}") - return tcr - - -# Fig 145 I3C Start Timing -def get_i3c_stop_timings(spec, sys_clk=100e6): - sys_period = f2T(sys_clk) - spec = { - "f_scl_max": 12.5e6, - "t_cr_max": 12e-9, - "t_cbp_min": 38.4e-9 / 2, - } - T_PULL_SDA_HIGH = norm_ceil((spec["t_cr_max"] + spec["t_cbp_min"]), sys_period) - t = cycles2seconds(T_PULL_SDA_HIGH, sys_period) - - logging.info(f"T_PULL_SDA_HIGH={T_PULL_SDA_HIGH}") - logging.info(f"t={t}") - return t - - -def width_timing_csr(freq=1e9): - """ - Need to determine max needed width of CSRs, which control timings (setup, data, hold, etc.) - - Assumptions: - - maximum desired clock speed will be 1 GHz - - longest time, which we want to be able to measure is 1ms - - bus_idle is currently the longest time measured and equal to 200us - - multiple by 5 to provide error margin - - Timing registers will increment with each clock cycle, so timer resolution is equal to the clock period (1ns). - There are (longest_time/period) = (1ms / 1ns) = 1e6 ticks before timer reaches its max value. - CSR must be therefore at least clog2(1e6) = ceil(19.9) = 20 bits wide. - """ - period = f2T(freq) - t_longest = 1e-3 - max_value = math.ceil(t_longest / period) - logging.info(f"Maximum value stored in register = {max_value}") - bit_width = max_value.bit_length() - logging.info( - f"[NORMATIVE]::: Registers for timing configuration should be at least {bit_width} bits wide." - ) - assert bit_width <= 32 - - -def timing_registers_reset(): - """ - TODO: Calculate values (partially calculated in previous functions - consolidate data) - """ - T_R_REG = 0 - T_F_REG = 0 - TSU_DAT_REG = 0 - THD_DAT_REG = 0 - T_HIGH_REG = 0 - T_LOW_REG = 0 - T_HD_STA_REG = 0 - T_SU_STA_REG = 0 - T_SU_STO_REG = 0 - - logging.info(f"[NORMATIVE]::: Register T_R_REG should have reset value : {T_R_REG}") - logging.info(f"[NORMATIVE]::: Register T_F_REG should have reset value : {T_F_REG}") - logging.info(f"[NORMATIVE]::: Register TSU_DAT_REG should have reset value : {TSU_DAT_REG}") - logging.info(f"[NORMATIVE]::: Register THD_DAT_REG should have reset value : {THD_DAT_REG}") - logging.info(f"[NORMATIVE]::: Register T_HIGH_REG should have reset value : {T_HIGH_REG}") - logging.info(f"[NORMATIVE]::: Register T_LOW_REG should have reset value : {T_LOW_REG}") - logging.info(f"[NORMATIVE]::: Register T_HD_STA_REG should have reset value : {T_HD_STA_REG}") - logging.info(f"[NORMATIVE]::: Register T_SU_STA_REG should have reset value : {T_SU_STA_REG}") - logging.info(f"[NORMATIVE]::: Register T_SU_STO_REG should have reset value : {T_SU_STO_REG}") - - -def bus_condition_timing_register(freq=3e8): - """ - Bus Condition Timing - - 1. If clock speed changes, then registers must be udpated - 2. If bus configuration (pure, mixed) changes, then registers must be updated - """ - t_free = [38.4e-9, 0.5e-6, 1.3e-6] # pure, mixed fm+, mixed - t_aval = 1e-6 - t_idle = 200e-6 - t_hdr_timeout = 60e-6 # I3C spec ยง5.1.10.1.9 - Optional HDR error recovery - - period = f2T(freq) - - T_FREE = norm_ceil(t_free[0], period) - T_IDLE = norm_ceil(t_aval, period) - T_AVAL = norm_ceil(t_idle, period) - T_HDR_TIMEOUT = norm_ceil(t_hdr_timeout, period) - - logging.info( - f"[NORMATIVE]::: Register T_FREE should have reset value : {T_FREE} or {hex(T_FREE)}" - ) - logging.info( - f"[NORMATIVE]::: Register T_IDLE should have reset value : {T_IDLE} or {hex(T_IDLE)}" - ) - logging.info( - f"[NORMATIVE]::: Register T_AVAL should have reset value : {T_AVAL} or {hex(T_AVAL)}" - ) - logging.info( - f"[NORMATIVE]::: Register T_HDR_TIMEOUT should have reset value : {T_HDR_TIMEOUT} or {hex(T_HDR_TIMEOUT)}" - ) - - -def main(): - setup_logger() - # Expected system frequency - # TODO: Allow setting clock frequency with a script parameter - sys_freq = 333.333e6 - sys_period = 1 / sys_freq - logging.info(f"sys_freq = {EN(sys_freq)}") - logging.info(f"sys_period = {EN(sys_period)}") - for mode in [IXCModes.LEGACY_400k, IXCModes.LEGACY_1M]: - logging.info("*** FW Settings ***") - logging.info(f"mode = {mode}") - log_generic_timings(mode, sys_freq) - spec = IXCSpecification(mode) - get_firmware_settings(spec=spec, sys_clk=sys_freq) - - bus_freq = 12.5e6 - bus_period = f2T(bus_freq) - logging.info("\033[92mI3C :: SDR PUSH-PULL TIMINGS\033[0m") - logging.info("\033[92mI3C :: RISE FALL\033[0m") - get_i3c_rise_fall_timings(bus_period) - - logging.info("\033[92mI3C :: START\033[0m") - get_i3c_start_timings() - - logging.info("\033[92mI3C :: SDR \033[0m") - get_i3c_sdr_timings(spec=None, sys_clk=sys_freq) - - logging.info("\033[92mI3C :: STOP TIMINGS\033[0m") - get_i3c_stop_timings(spec=None, sys_clk=sys_freq) - - get_i3c_firmware_settings() - - -if __name__ == "__main__": - main() diff --git a/tools/timing/timings.py b/tools/timing/timings.py new file mode 100644 index 000000000..6a0f2504f --- /dev/null +++ b/tools/timing/timings.py @@ -0,0 +1,233 @@ +# SPDX-License-Identifier: Apache-2.0 + +import logging +import math +import argparse + +# --- 1. BASE SPECIFICATION CONSTRAINTS --- +# Absolute minimums according to the I3C Basic/I2C FM spec (in nanoseconds) +I3C_MIN_TIMINGS_NS = { + "T_R": 0.0, # Managed physically + "T_F": 0.0, # Managed physically + "T_SU_DAT": 3.0, + "T_SU_DAT_I2C": 100.0, + "T_HD_DAT": 0.0, + "T_HIGH": 24.0, # I3C PP High >= 24 ns + "T_HIGH_OD": 24.0, # I3C OD High >= 24 ns + "T_HIGH_INIT_OD": 200.0, # I3C Init OD High >= 200 ns + "T_HIGH_I2C": 600.0, + "T_LOW": 24.0, # I3C PP Low >= 24 ns + "T_LOW_OD": 200.0, # I3C OD Low >= 200 ns + "T_LOW_I2C": 1300.0, + "T_HD_STA": 38.4, + "T_HD_STA_I2C": 600.0, + "T_HD_RSTA": 19.2, + "T_SU_STA": 19.2, + "T_SU_STA_I2C": 600.0, + "T_SU_STO": 19.2, + "T_SU_STO_I2C": 600.0, + "T_DS_OD": 19.2, + "T_FREE": 38.4, + "T_FREE_I2C": 1300.0, + "T_AVAL": 1000.0, + "T_IDLE": 200000.0, +} + +def validate_timings(timings: dict, f_sys: float) -> bool: + """ + Validates a dictionary of timing registers against the I3C Basic spec. + """ + t_sys_ns = 1e9 / f_sys + is_valid = True + + # 1. Check individual minimum constraints + for key, min_ns in I3C_MIN_TIMINGS_NS.items(): + if key not in timings: + logging.error(f"Validation Error: Missing timing parameter '{key}'") + is_valid = False + continue + + min_cycles = math.ceil(min_ns / t_sys_ns) + + if timings[key] < min_cycles: + logging.error( + f"Validation Error: {key} ({timings[key]} cycles) is less than " + f"the minimum spec requirement ({min_cycles} cycles)." + ) + is_valid = False + + if "T_HIGH" in timings and "T_LOW" in timings: + # T_HIGH + T_LOW must be >= 80 ns (max frequency is 12.5 MHz) + period_ns = (timings["T_HIGH"] + timings["T_LOW"]) * t_sys_ns + if period_ns < 80.0: + logging.error( + f"Validation Error: I3C SDR Period ({period_ns:.1f} ns) " + f"violates the 12.5 MHz max frequency (Must be >= 80.0 ns)." + ) + is_valid = False + + if is_valid: + logging.info("All timings validated successfully against I3C Basic Spec!") + + return is_valid + +def generate_timings(f_scl: float, f_sys: float, duty_cycle: float = 0.5) -> dict: + """ + Generates valid I3C timing registers (in clock cycles) based on target SCL freq, + system clock freq, and desired duty cycle. Warns if requested timings are overridden. + """ + MAX_F_SCL = 12.9e6 + + # Enforce maximum f_SCL limit + if f_scl > MAX_F_SCL: + logging.error( + f"Requested f_SCL ({f_scl/1e6:.2f} MHz) exceeds the maximum allowed " + f"limit! Clamping f_SCL to {MAX_F_SCL/1e6:.2f} MHz." + ) + f_scl = MAX_F_SCL + + t_sys_ns = 1e9 / f_sys + t_scl_ns = 1e9 / f_scl + + # Calculate target high/low times in ns based on SCL frequency and duty cycle + t_high_req_ns = t_scl_ns * duty_cycle + t_low_req_ns = t_scl_ns * (1.0 - duty_cycle) + + min_t_high = I3C_MIN_TIMINGS_NS["T_HIGH"] + min_t_low = I3C_MIN_TIMINGS_NS["T_LOW"] + + # Check if the requested parameters violate the baseline I3C specs + if t_high_req_ns < min_t_high or t_low_req_ns < min_t_low: + logging.error( + f"Target f_SCL ({f_scl/1e6:.2f} MHz) with {duty_cycle*100:.0f}% duty cycle " + f"yields T_HIGH={t_high_req_ns:.1f}ns and T_LOW={t_low_req_ns:.1f}ns. " + f"This violates I3C limits! Overriding to safe minimums " + f"(T_HIGH>={min_t_high}ns, T_LOW>={min_t_low}ns)." + ) + + # Enforce standard spec minimums so we don't accidentally violate them + t_high_ns = max(t_high_req_ns, min_t_high) + t_low_ns = max(t_low_req_ns, min_t_low) + + # Create a working copy of our timings in ns + timings_ns = I3C_MIN_TIMINGS_NS.copy() + timings_ns["T_HIGH"] = t_high_ns + timings_ns["T_LOW"] = t_low_ns + + # Convert all nanosecond values to system clock cycles + def ns_to_cycles(ns: float) -> int: + return math.ceil(ns / t_sys_ns) + + timings_cycles = {reg: ns_to_cycles(ns) for reg, ns in timings_ns.items()} + + assert validate_timings(timings=timings_cycles, f_sys=f_sys) + + return timings_cycles + +def log_timing_configuration(timings: dict, f_sys: float = None): + """ + Nicely formats and logs the current timing configuration. + If f_sys is provided, it also calculates the actual time in nanoseconds. + """ + logging.info("=== I3C Timing Configuration ===") + + if f_sys: + t_sys_ns = 1e9 / f_sys + logging.info(f"{'Register':<16} | {'Cycles':<8} | {'Time (ns)':<10}") + logging.info("-" * 40) + for key, cycles in timings.items(): + time_ns = cycles * t_sys_ns + logging.info(f"{key:<16} | {cycles:<8} | {time_ns:.2f}") + else: + logging.info(f"{'Register':<16} | {'Cycles':<8}") + logging.info("-" * 28) + for key, cycles in timings.items(): + logging.info(f"{key:<16} | {cycles:<8}") + + logging.info("================================") + +def print_myst_table(timings: dict, f_sys: float, target_name: str): + """Prints the timing configuration as a MyST list-table.""" + t_sys_ns = 1e9 / f_sys + target_scl_mhz = 1e9 / (timings['T_HIGH']*t_sys_ns + timings['T_LOW']*t_sys_ns) / 1e6 + + # Safe reference name (lowercase, no spaces) + ref_name = f"timing-csr-{target_name.lower().replace(' ', '-')}" + + # Print header context outside the table + print(f"### {target_name} Configuration\n") + print(f"**System Clock:** {f_sys / 1e6:.2f} MHz | **Target SCL:** {target_scl_mhz:.2f} MHz\n") + + # Print the MyST table + print(f":::{{list-table}} {target_name} Timing Registers") + print(f":name: {ref_name}") + print(":widths: 40 30 30") + print(":header-rows: 1\n") + + # Table Headers + print("* - **Register**") + print(" - **Cycles**") + print(" - **Time (ns)**") + + # Table Data + for key, cycles in timings.items(): + time_ns = cycles * t_sys_ns + print(f"* - {key}") + print(f" - {cycles}") + print(f" - {time_ns:.2f}") + + print(":::\n") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate and validate I3C timings.") + + parser.add_argument( + "--freq", + type=float, + default=200.0e6, + help="System clock frequency in Hz (Default: 200.0e6)" + ) + + parser.add_argument( + "--bus_freq", + type=float, + default=12.5e6, + help="Target I3C bus frequency in Hz (Default: 12.5e6)" + ) + + parser.add_argument( + "--duty_cycle", + type=float, + default=0.5, + help="Target duty cycle (Default: 0.5)" + ) + + parser.add_argument( + "--md", + action="store_true", + help="Output the results as a MyST Markdown table (suppresses standard logging)" + ) + + parser.add_argument( + "--target_name", + type=str, + default="I3C Target", + help="Name of the configuration for the Markdown header (e.g., 'FPGA', 'ASIC')" + ) + + args = parser.parse_args() + + log_level = logging.WARNING if args.md else logging.INFO + logging.basicConfig(level=log_level, force=True) + + if not args.md: + logging.info(f"Generating timings for System Clock: {args.freq / 1e6:.2f} MHz, Bus Clock: {args.bus_freq / 1e6:.2f} MHz") + + # Generate the timings + timings = generate_timings(f_scl=args.bus_freq, f_sys=args.freq, duty_cycle=args.duty_cycle) + + # Output the results based on the flags + if args.md: + print_myst_table(timings, f_sys=args.freq, target_name=args.target_name) + else: + log_timing_configuration(timings, f_sys=args.freq)