Merge pull request #144 from mithro/nextpnr-migen-update
[litex.git] / litex / build / sim / verilator.py
1 # This file is Copyright (c) 2015-2018 Florent Kermarrec <florent@enjoy-digital.fr>
2 # 2017 Pierre-Olivier Vauboin <po@lambdaconcept.com>
3 # License: BSD
4
5 import os
6 import sys
7 import subprocess
8
9 from migen.fhdl.structure import _Fragment
10
11 from litex.build import tools
12 from litex.build.generic_platform import *
13
14
15 sim_directory = os.path.abspath(os.path.dirname(__file__))
16 core_directory = os.path.join(sim_directory, 'core')
17
18
19 def _generate_sim_h_struct(name, index, siglist):
20 content = ''
21
22 content += 'struct pad_s {}{}[] = {{\n'.format(name, index)
23 for signame, sigbits, dummy in siglist:
24 content += ' {{ (char*)"{}", {}, NULL }},\n'.format(signame, sigbits)
25 content += ' { NULL, 0, NULL }\n'
26 content += '};\n\n'
27
28 return content
29
30
31 def _generate_sim_h(platform):
32 content = """\
33 #ifndef __SIM_CORE_H_
34 #define __SIM_CORE_H_
35 #include "pads.h"
36
37 """
38 for args in platform.sim_requested:
39 content += _generate_sim_h_struct(*args)
40
41 content += """\
42 #ifndef __cplusplus
43 void litex_sim_init(void **out);
44 #endif
45
46 #endif /* __SIM_CORE_H_ */
47 """
48 tools.write_to_file("dut_header.h", content)
49
50
51 def _generate_sim_cpp_struct(name, index, siglist):
52 content = ''
53
54 for i, (signame, sigbits, sigfname) in enumerate(siglist):
55 content += ' {}{}[{}].signal = &dut->{};\n'.format(name, index, i, sigfname)
56
57 idx_int = 0 if not index else int(index)
58 content += ' litex_sim_register_pads({}{}, (char*)"{}", {});\n\n'.format(name, index, name, idx_int)
59
60 return content
61
62
63 def _generate_sim_cpp(platform, trace=False):
64 content = """\
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include "Vdut.h"
69 #include <verilated.h>
70 #include "dut_header.h"
71
72 extern "C" void litex_sim_init_tracer(void *vdut);
73 extern "C" void litex_sim_tracer_dump();
74
75 extern "C" void litex_sim_dump()
76 {
77 """
78 if trace:
79 content += """\
80 litex_sim_tracer_dump();
81 """
82 content += """\
83 }
84
85 extern "C" void litex_sim_init(void **out)
86 {
87 Vdut *dut;
88
89 dut = new Vdut;
90
91 litex_sim_init_tracer(dut);
92
93 """
94 for args in platform.sim_requested:
95 content += _generate_sim_cpp_struct(*args)
96
97 content += """\
98 *out=dut;
99 }
100 """
101 tools.write_to_file("dut_init.cpp", content)
102
103
104 def _generate_sim_variables(include_paths):
105 include = ""
106 for path in include_paths:
107 include += "-I"+path+" "
108 content = """\
109 SRC_DIR = {}
110 INC_DIR = {}
111 """.format(core_directory, include)
112 tools.write_to_file("variables.mak", content)
113
114
115 def _generate_sim_config(config):
116 content = config.get_json()
117 tools.write_to_file("sim_config.js", content)
118
119
120 def _build_sim(platform, build_name, sources, threads, coverage, verbose):
121 makefile = os.path.join(core_directory, 'Makefile')
122 cc_srcs = []
123 for filename, language, library in sources:
124 cc_srcs.append("--cc " + filename + " ")
125 build_script_contents = """\
126 rm -rf obj_dir/
127 make -C . -f {} {} {} {}
128 mkdir -p modules && cp obj_dir/*.so modules
129 """.format(makefile,
130 "CC_SRCS=\"{}\"".format("".join(cc_srcs)),
131 "THREADS={}".format(threads) if int(threads) > 1 else "",
132 "COVERAGE=1" if coverage else "",
133 )
134 build_script_file = "build_" + build_name + ".sh"
135 tools.write_to_file(build_script_file, build_script_contents, force_unix=True)
136
137 p = subprocess.Popen(["bash", build_script_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
138 output, _ = p.communicate()
139 output = output.decode('utf-8')
140 if p.returncode != 0:
141 error_messages = []
142 for l in output.splitlines():
143 if verbose or "error" in l.lower():
144 error_messages.append(l)
145 raise OSError("Subprocess failed with {}\n{}".format(p.returncode, "\n".join(error_messages)))
146 if verbose:
147 print(output)
148
149
150 def _run_sim(build_name, as_root=False):
151 run_script_contents = "sudo " if as_root else ""
152 run_script_contents += "obj_dir/Vdut"
153 run_script_file = "run_" + build_name + ".sh"
154 tools.write_to_file(run_script_file, run_script_contents, force_unix=True)
155 if sys.platform != "win32":
156 import termios
157 termios_settings = termios.tcgetattr(sys.stdin.fileno())
158 try:
159 r = subprocess.call(["bash", run_script_file])
160 if r != 0:
161 raise OSError("Subprocess failed")
162 except:
163 pass
164 if sys.platform != "win32":
165 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, termios_settings)
166
167
168 class SimVerilatorToolchain:
169 def build(self, platform, fragment, build_dir="build", build_name="dut",
170 toolchain_path=None, serial="console", build=True, run=True, threads=1,
171 verbose=True, sim_config=None, trace=False, coverage=False):
172
173 # create build directory
174 os.makedirs(build_dir, exist_ok=True)
175 os.chdir(build_dir)
176
177 if build:
178 # finalize design
179 if not isinstance(fragment, _Fragment):
180 fragment = fragment.get_fragment()
181 platform.finalize(fragment)
182
183 # generate top module
184 top_output = platform.get_verilog(fragment,
185 name=build_name, dummy_signal=False, regular_comb=False, blocking_assign=True)
186 named_sc, named_pc = platform.resolve_signals(top_output.ns)
187 top_file = build_name + ".v"
188 top_output.write(top_file)
189 platform.add_source(top_file)
190
191 # generate cpp header/main/variables
192 _generate_sim_h(platform)
193 _generate_sim_cpp(platform, trace)
194 _generate_sim_variables(platform.verilog_include_paths)
195
196 # generate sim config
197 if sim_config:
198 _generate_sim_config(sim_config)
199
200 # build
201 _build_sim(platform, build_name, platform.sources, threads, coverage, verbose)
202
203 # run
204 if run:
205 _run_sim(build_name, as_root=sim_config.has_module("ethernet"))
206
207 os.chdir("../../")
208
209 if build:
210 return top_output.ns