244 lines
8.5 KiB
Python
244 lines
8.5 KiB
Python
import csv
|
||
from pathlib import Path
|
||
|
||
# ==========================
|
||
# НАСТРОЙКИ ПОЛЬЗОВАТЕЛЯ
|
||
# ==========================
|
||
|
||
# Входной файл из Logic 2 (CSV только с Time [s] и D7)
|
||
CSV_FILE = "digital_cut_50.csv"
|
||
|
||
# Выходной Verilog-файл
|
||
VERILOG_FILE = "pattern_rom.v"
|
||
|
||
# Тактовая частота в МК/FPGA, Гц (должна совпадать с тем, что реально в железе)
|
||
F_CLK_HZ = 48_000_000 # пример: 48 МГц
|
||
|
||
# Имена колонок в CSV (посмотри заголовок в своём файле и подгони при необходимости)
|
||
TIME_COL = "Time [s]" # колонка с временем
|
||
LEVEL_COL = "Channel 7" # колонка с уровнем сигнала
|
||
|
||
# Сколько бит отвести под счётчик тиков в 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() |