|
1 | 1 | # SPDX-License-Identifier: BSD-2-Clause |
2 | 2 |
|
3 | 3 | import os |
| 4 | +import sys |
4 | 5 | from pathlib import Path |
5 | 6 |
|
6 | 7 | from amaranth import * |
| 8 | +from amaranth.lib import io |
7 | 9 | from amaranth.back import rtlil |
| 10 | +from amaranth.hdl._ir import PortDirection |
| 11 | +from amaranth.lib.cdc import FFSynchronizer |
8 | 12 |
|
| 13 | +from .. import ChipFlowError |
| 14 | +from .utils import load_pinlock |
9 | 15 |
|
10 | 16 | __all__ = ["SimPlatform"] |
11 | 17 |
|
12 | 18 |
|
13 | 19 | class SimPlatform: |
14 | | - def __init__(self): |
| 20 | + |
| 21 | + def __init__(self, config): |
15 | 22 | self.build_dir = os.path.join(os.environ['CHIPFLOW_ROOT'], 'build', 'sim') |
16 | 23 | self.extra_files = dict() |
17 | | - self.clk = Signal() |
18 | | - self.rst = Signal() |
19 | | - self.buttons = Signal(2) |
20 | 24 | self.sim_boxes = dict() |
| 25 | + self._ports = {} |
| 26 | + self._config = config |
21 | 27 |
|
22 | 28 | def add_file(self, filename, content): |
23 | 29 | if not isinstance(content, (str, bytes)): |
24 | 30 | content = content.read() |
25 | 31 | self.extra_files[filename] = content |
26 | 32 |
|
27 | | - def add_model(self, inst_type, iface, edge_det=[]): |
28 | | - conns = dict(i_clk=ClockSignal(), a_keep=True) |
29 | | - |
30 | | - def is_model_out(field_name): |
31 | | - assert field_name.endswith("_o") or field_name.endswith("_oe") or field_name.endswith("_i"), field_name |
32 | | - return field_name.endswith("_i") |
33 | | - for field_name in iface.signature.members: |
34 | | - if is_model_out(field_name): |
35 | | - conns[f"o_{field_name}"] = getattr(iface, field_name) |
36 | | - else: |
37 | | - conns[f"i_{field_name}"] = getattr(iface, field_name) |
38 | | - if inst_type not in self.sim_boxes: |
39 | | - box = 'attribute \\blackbox 1\n' |
40 | | - box += 'attribute \\cxxrtl_blackbox 1\n' |
41 | | - box += 'attribute \\keep 1\n' |
42 | | - box += f'module \\{inst_type}\n' |
43 | | - box += ' attribute \\cxxrtl_edge "a"\n' |
44 | | - box += ' wire width 1 input 0 \\clk\n' |
45 | | - for i, ((field_name,), _, field) in enumerate(iface.signature.flatten(iface)): |
46 | | - field_width = Shape.cast(field.shape()).width |
47 | | - if field_name in edge_det: |
48 | | - box += ' attribute \\cxxrtl_edge "a"\n' |
49 | | - box += f' wire width {field_width} {"output" if is_model_out(field_name) else "input"} {i} \\{field_name}\n' # noqa: E501 |
50 | | - box += 'end\n\n' |
51 | | - self.sim_boxes[inst_type] = box |
52 | | - return Instance(inst_type, **conns) |
53 | | - |
54 | | - def add_monitor(self, inst_type, iface): |
55 | | - conns = dict(i_clk=ClockSignal(), a_keep=True) |
56 | | - for field_name in iface.signature.members: |
57 | | - conns[f'i_{field_name}'] = getattr(iface, field_name) |
58 | | - if inst_type not in self.sim_boxes: |
59 | | - box = 'attribute \\blackbox 1\n' |
60 | | - box += 'attribute \\cxxrtl_blackbox 1\n' |
61 | | - box += 'attribute \\keep 1\n' |
62 | | - box += f'module \\{inst_type}\n' |
63 | | - box += ' attribute \\cxxrtl_edge "a"\n' |
64 | | - box += ' wire width 1 input 0 \\clk\n' |
65 | | - for i, ((field_name,), _, field) in enumerate(iface.signature.flatten(iface)): |
66 | | - field_width = Shape.cast(field.shape()).width |
67 | | - box += f' wire width {field_width} input {i + 1} \\{field_name}\n' |
68 | | - box += 'end\n\n' |
69 | | - self.sim_boxes[inst_type] = box |
70 | | - return Instance(inst_type, **conns) |
71 | | - |
72 | 33 | def build(self, e): |
73 | 34 | Path(self.build_dir).mkdir(parents=True, exist_ok=True) |
74 | 35 |
|
75 | | - output = rtlil.convert(e, name="sim_top", ports=[self.clk, self.rst, self.buttons], platform=self) |
| 36 | + ports = [] |
| 37 | + for port_name, port in self._ports.items(): |
| 38 | + if port.direction in (io.Direction.Input, io.Direction.Bidir): |
| 39 | + ports.append((f"io${port_name}$i", port.i, PortDirection.Input)) |
| 40 | + if port.direction in (io.Direction.Output, io.Direction.Bidir): |
| 41 | + ports.append((f"io${port_name}$o", port.o, PortDirection.Output)) |
| 42 | + if port.direction is io.Direction.Bidir: |
| 43 | + ports.append((f"io${port_name}$oe", port.oe, PortDirection.Output)) |
| 44 | + |
| 45 | + output = rtlil.convert(e, name="sim_top", ports=ports, platform=self) |
76 | 46 |
|
77 | 47 | top_rtlil = Path(self.build_dir) / "sim_soc.il" |
78 | 48 | with open(top_rtlil, "w") as rtlil_file: |
79 | 49 | for box_content in self.sim_boxes.values(): |
80 | 50 | rtlil_file.write(box_content) |
81 | 51 | rtlil_file.write(output) |
| 52 | + |
82 | 53 | top_ys = Path(self.build_dir) / "sim_soc.ys" |
83 | 54 | with open(top_ys, "w") as yosys_file: |
84 | 55 | for extra_filename, extra_content in self.extra_files.items(): |
85 | 56 | extra_path = Path(self.build_dir) / extra_filename |
86 | 57 | with open(extra_path, "w") as extra_file: |
87 | 58 | extra_file.write(extra_content) |
88 | 59 | if extra_filename.endswith(".il"): |
89 | | - print(f"read_rtlil {extra_filename}", file=yosys_file) |
| 60 | + print(f"read_rtlil {extra_path}", file=yosys_file) |
90 | 61 | else: |
91 | 62 | # FIXME: use -defer (workaround for YosysHQ/yosys#4059) |
92 | | - print(f"read_verilog {extra_filename}", file=yosys_file) |
| 63 | + print(f"read_verilog {extra_path}", file=yosys_file) |
93 | 64 | print("read_rtlil sim_soc.il", file=yosys_file) |
94 | 65 | print("hierarchy -top sim_top", file=yosys_file) |
95 | | - # FIXME: use the default -O6 (workaround for YosysHQ/yosys#4227) |
96 | | - print("write_cxxrtl -O4 -header sim_soc.cc", file=yosys_file) |
| 66 | + print("write_cxxrtl -header sim_soc.cc", file=yosys_file) |
| 67 | + |
| 68 | + def instantiate_ports(self, m: Module): |
| 69 | + if hasattr(self, "_pinlock"): |
| 70 | + return |
| 71 | + |
| 72 | + pinlock = load_pinlock() |
| 73 | + for component, iface in pinlock.port_map.items(): |
| 74 | + for k, v in iface.items(): |
| 75 | + for name, port in v.items(): |
| 76 | + self._ports[port.port_name] = io.SimulationPort(port.direction, port.width, invert=port.invert, name=f"{component}-{name}") |
| 77 | + |
| 78 | + for clock, name in self._config["chipflow"]["clocks"].items(): |
| 79 | + if name not in pinlock.package.clocks: |
| 80 | + raise ChipFlowError("Unable to find clock {name} in pinlock") |
| 81 | + |
| 82 | + port_data = pinlock.package.clocks[name] |
| 83 | + port = io.SimulationPort(io.Direction.Input, port_data.width, invert=True, name=f"clock-{name}") |
| 84 | + self._ports[name] = port |
| 85 | + |
| 86 | + if clock == 'default': |
| 87 | + clock = 'sync' |
| 88 | + setattr(m.domains, clock, ClockDomain(name=clock)) |
| 89 | + clk_buffer = io.Buffer("i", port) |
| 90 | + setattr(m.submodules, "clk_buffer_" + clock, clk_buffer) |
| 91 | + m.d.comb += ClockSignal().eq(clk_buffer.i) |
| 92 | + |
| 93 | + for reset, name in self._config["chipflow"]["resets"].items(): |
| 94 | + port_data = pinlock.package.resets[name] |
| 95 | + port = io.SimulationPort(io.Direction.Input, port_data.width, invert=port.invert, name=f"clock-{name}", ) |
| 96 | + self._ports[name] = port |
| 97 | + rst_buffer = io.Buffer("i", port) |
| 98 | + setattr(m.submodules, reset, rst_buffer) |
| 99 | + setattr(m.submodules, reset + "_sync", FFSynchronizer(rst_buffer.i, ResetSignal())) |
| 100 | + |
| 101 | + self._pinlock = pinlock |
| 102 | + |
| 103 | + |
| 104 | + |
| 105 | + |
| 106 | +VARIABLES = { |
| 107 | + "OUTPUT_DIR": "./build/sim", |
| 108 | + "ZIG_CXX": f"{sys.executable} -m ziglang c++", |
| 109 | + "CXXFLAGS": "-O3 -g -std=c++17 -Wno-array-bounds -Wno-shift-count-overflow -fbracket-depth=1024", |
| 110 | + "DEFINES": "-DPROJECT_ROOT=\\\"{PROJECT_ROOT}\\\" -DBUILD_DIR=\\\"{BUILD_DIR}\\\"", |
| 111 | + "INCLUDES": "-I {OUTPUT_DIR} -I {COMMON_DIR} -I {COMMON_DIR}/vendor -I {RUNTIME_DIR}", |
| 112 | +} |
| 113 | +DOIT_CONFIG = {'action_string_formatting': 'both'} |
| 114 | + |
| 115 | +BUILD_SIM = { |
| 116 | + "name": "build_sim", |
| 117 | + "actions": [ |
| 118 | + "{ZIG_CXX} {CXXFLAGS} {INCLUDES} {DEFINES} -o {OUTPUT_DIR}/sim_soc{EXE} " |
| 119 | + "{OUTPUT_DIR}/sim_soc.cc {SOURCE_DIR}/main.cc {COMMON_DIR}/models.cc" |
| 120 | + ], |
| 121 | + "targets": [ |
| 122 | + "{OUTPUT_DIR}/sim_soc{EXE}" |
| 123 | + ], |
| 124 | + "file_dep": [ |
| 125 | + "{OUTPUT_DIR}/sim_soc.cc", |
| 126 | + "{OUTPUT_DIR}/sim_soc.h", |
| 127 | + "{SOURCE_DIR}/main.cc", |
| 128 | + "{COMMON_DIR}/models.cc", |
| 129 | + "{COMMON_DIR}/models.h", |
| 130 | + "{COMMON_DIR}/vendor/nlohmann/json.hpp", |
| 131 | + "{COMMON_DIR}/vendor/cxxrtl/cxxrtl_server.h", |
| 132 | + ], |
| 133 | + } |
| 134 | + |
| 135 | +SIM_CXXRTL = { |
| 136 | + "name": "sim_cxxrtl", |
| 137 | + "actions": ["cd {OUTPUT_DIR} && pdm run yowasp-yosys sim_soc.ys"], |
| 138 | + "targets": ["{OUTPUT_DIR}/sim_soc.cc", "{OUTPUT_DIR}/sim_soc.h"], |
| 139 | + "file_dep": ["{OUTPUT_DIR}/sim_soc.ys", "{OUTPUT_DIR}/sim_soc.il"], |
| 140 | + } |
| 141 | + |
| 142 | +TASKS = [BUILD_SIM, SIM_CXXRTL] |
| 143 | + |
| 144 | + |
0 commit comments