import csv from pathlib import Path # ========================== # НАСТРОЙКИ ПОЛЬЗОВАТЕЛЯ # ========================== # Входной файл из Logic 2 (CSV только с Time [s] и D7) CSV_FILE = "Logic2_D7.csv" # Выходной Verilog-файл VERILOG_FILE = "pattern_rom.v" # Тактовая частота в МК/FPGA, Гц (должна совпадать с тем, что реально в железе) F_CLK_HZ = 48_000_000 # пример: 48 МГц # Имена колонок в CSV (посмотри заголовок в своём файле и подгони при необходимости) TIME_COL = "Time [s]" # колонка с временем LEVEL_COL = "D7" # колонка с уровнем сигнала # Сколько бит отвести под счётчик тиков в Verilog TICKS_WIDTH = 32 # ========================== # ЧТЕНИЕ CSV ИЗ LOGIC 2 # ========================== def read_digital_trace(csv_path: str): """ Читает CSV-файл в формате Logic 2 и возвращает два списка: times[i] - время в секундах (float), levels[i] - уровень (0 или 1) в моменты времени times[i]. Ожидается, что первая строка CSV — заголовок с колонками TIME_COL и LEVEL_COL. """ times = [] levels = [] with open(csv_path, newline="") as f: reader = csv.DictReader(f) # Проверяем наличие нужных столбцов if TIME_COL not in reader.fieldnames or LEVEL_COL not in reader.fieldnames: raise RuntimeError( f"В CSV нет ожидаемых колонок '{TIME_COL}' и/или '{LEVEL_COL}'. " f"Найденные столбцы: {reader.fieldnames}" ) # Проходим по всем строкам после заголовка for row in reader: # Время t = float(row[TIME_COL]) # Уровень (0 или 1) lvl = int(row[LEVEL_COL]) times.append(t) levels.append(lvl) if not times: raise RuntimeError("CSV пустой или не содержит данных (только заголовок).") return times, levels # ========================== # ПОСТРОЕНИЕ СЕГМЕНТОВ # ========================== def build_segments(times, levels): """ Строит сегменты постоянного уровня. На входе: times[i], levels[i] — выборки (временная метка + уровень). На выходе: segments: список кортежей (level, duration_sec), где: level — 0 или 1, duration_sec — длительность в секундах (> 0). Пример: times = [0.0, 1e-6, 2e-6, 3e-6] levels = [0, 0, 1, 1 ] → segments = [(0, 2e-6), (1, 1e-6)] """ segments = [] current_level = levels[0] start_time = times[0] # Идём по всем точкам, начиная со второй for i in range(1, len(times)): t = times[i] lvl = levels[i] # Если уровень поменялся — значит, закончился сегмент if lvl != current_level: duration = t - start_time if duration > 0: segments.append((current_level, duration)) # Начинаем новый сегмент с новым уровнем start_time = t current_level = lvl # Последний сегмент: от последней смены до конца записи end_time = times[-1] duration = end_time - start_time if duration > 0: segments.append((current_level, duration)) # Если ни одного сегмента не получилось (например, уровень вообще не менялся) if not segments: full_duration = times[-1] - times[0] if full_duration <= 0: raise RuntimeError("Не удалось построить сегменты: время не растёт.") segments.append((levels[0], full_duration)) return segments # ========================== # КВАНТОВАНИЕ В ТИКИ # ========================== def quantize_segments(segments, f_clk_hz: float): """ Переводит длительность сегментов из секунд в количество тиков таймера. На входе: segments: список (level, duration_sec) На выходе: segments_ticks: список (level, ticks) ticks — целое положительное число (>= 1) """ result = [] for idx, (level, duration_sec) in enumerate(segments): # Перевод секунд в тики ticks = round(duration_sec * f_clk_hz) # Страховка от нулевой длительности if ticks <= 0: ticks = 1 result.append((level, ticks)) return result # ========================== # ГЕНЕРАЦИЯ VERILOG ROM # ========================== def generate_verilog_rom(segments_ticks, out_path: str): """ Генерирует Verilog-модуль pattern_rom с ROM вида: data = {level, ticks}, где level — самый старший бит (1 бит), ticks — младшие TICKS_WIDTH бит. Интерфейс модуля: module pattern_rom #( parameter TICKS_WIDTH = ..., parameter DEPTH = ... )( input wire [$clog2(DEPTH)-1:0] addr, output reg [TICKS_WIDTH:0] data ); // data[TICKS_WIDTH] - уровень (0 или 1) // data[TICKS_WIDTH-1:0] - ticks """ depth = len(segments_ticks) lines = [] lines.append(f"// Автогенерация из CSV: {CSV_FILE}") lines.append(f"// F_CLK_HZ = {F_CLK_HZ}") lines.append(f"// Количество сегментов (DEPTH) = {depth}") lines.append("") lines.append("module pattern_rom #(") lines.append(f" parameter TICKS_WIDTH = {TICKS_WIDTH},") lines.append(f" parameter DEPTH = {depth}") lines.append(") (") lines.append(" input wire [$clog2(DEPTH)-1:0] addr,") lines.append(" output reg [TICKS_WIDTH:0] data // {level[MSB], ticks[LSB:0]}") lines.append(");") lines.append("") lines.append(" // Простейший ROM на case по адресу") lines.append(" always @* begin") lines.append(" case (addr)") for i, (level, ticks) in enumerate(segments_ticks): # Проверяем, помещается ли длительность в TICKS_WIDTH бит if ticks >= (1 << TICKS_WIDTH): raise ValueError( f"Сегмент {i}: ticks={ticks} не помещается в {TICKS_WIDTH} бит. " "Увеличьте TICKS_WIDTH или уменьшите F_CLK_HZ." ) # Строка ROM: # i: data = {1'bL, TICKS_WIDTH'dticks}; lines.append( f" {i}: data = {{1'b{level}, {TICKS_WIDTH}'d{ticks}}};" ) # Значение по умолчанию (на всякий случай) lines.append(" default: data = {1'b0, {TICKS_WIDTH{1'b0}}};") lines.append(" endcase") lines.append(" end") lines.append("endmodule") lines.append("") # Запись Verilog-файла Path(out_path).write_text("\n".join(lines), encoding="utf-8") # ========================== # ТОЧКА ВХОДА # ========================== def main(): # 1. Читаем CSV Logic 2 times, levels = read_digital_trace(CSV_FILE) # 2. Строим сегменты постоянного уровня segments = build_segments(times, levels) # 3. Переводим длительность сегментов в тики тактового генератора segments_ticks = quantize_segments(segments, F_CLK_HZ) # 4. Генерируем Verilog ROM generate_verilog_rom(segments_ticks, VERILOG_FILE) # Немного статистики в консоль print(f"OK: прочитано выборок: {len(times)}") print(f" построено сегментов: {len(segments_ticks)}") print(f" Verilog ROM записан: {VERILOG_FILE}") if __name__ == "__main__": main()