Skip to content

Commit 5e038eb

Browse files
authored
Merge pull request #2231 from daniellovell/docs-add-jtag-openocd
Incorporate RocketChip JTAG + GDB debugging instructions, improve docs/conf.py for local development
2 parents 91593f7 + 4da1cfc commit 5e038eb

File tree

2 files changed

+309
-30
lines changed

2 files changed

+309
-30
lines changed

docs/Advanced-Concepts/Chip-Communication.rst

Lines changed: 231 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -129,35 +129,250 @@ The default Chipyard designs instantiate the DTM configured to use JTAG (i.e. ``
129129
However, they also use TSI/Serialized-TL with FESVR in case the JTAG interface isn't used.
130130
This allows users to choose how to communicate with the DUT (use TSI or JTAG).
131131

132-
Debugging with JTAG
133-
~~~~~~~~~~~~~~~~~~~
132+
Debugging with JTAG + GDB
133+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
134134

135-
Roughly the steps to debug with JTAG in simulation are as follows:
135+
This section provides detailed instructions for using GNU debugger (GDB) to debug RISC-V programs running on the emulator, similar to debugging with Spike.
136136

137-
1. Build a Chipyard JTAG-enabled RTL design. Remember default Chipyard designs are JTAG ready.
137+
Generating the Remote Bit-Bang (RBB) Emulator
138+
+++++++++++++++++++++++++++++++++++++++++++++++++++
139+
140+
The objective of this section is to use GNU debugger to debug RISC-V programs running on the emulator in
141+
the same fashion as in `Spike <https://github.com/riscv/riscv-isa-sim#debugging-with-gdb>`_.
142+
143+
For that we need to add a Remote Bit-Bang client to the emulator. We can do so by extending our Config
144+
with ``JtagDTMSystem``, which will add a ``DebugTransportModuleJTAG`` to the DUT and connect a ``SimJTAG``
145+
module in the Test Harness. This will allow OpenOCD to interface with the emulator, and GDB can interface
146+
with OpenOCD. In the following example we add this Config alteration to
147+
``src/main/scala/system/Configs.scala``:
148+
149+
.. code-block:: scala
150+
151+
class DefaultConfigRBB extends Config(
152+
new WithJtagDTMSystem ++ new WithNBigCores(1) ++ new WithCoherentBusTopology ++ new BaseConfig)
153+
154+
class QuadCoreConfigRBB extends Config(
155+
new WithJtagDTMSystem ++ new WithNBigCores(4) ++ new WithCoherentBusTopology ++ new BaseConfig)
156+
157+
To build the emulator with ``DefaultConfigRBB`` configuration we use the command:
138158

139159
.. code-block:: bash
140160
141-
cd sims/verilator
142-
# or
143-
cd sims/vcs
161+
rocket-chip$ cd emulator
162+
emulator$ CONFIG=freechips.rocketchip.system.DefaultConfigRBB make
163+
164+
We can also build a debug version capable of generating VCD waveforms using the command:
165+
166+
.. code-block:: bash
167+
168+
emulator$ CONFIG=freechips.rocketchip.system.DefaultConfigRBB make debug
169+
170+
By default the emulator is generated under the name
171+
``emulator-freechips.rocketchip.system-DefaultConfigRBB`` in the first case and
172+
``emulator-freechips.rocketchip.system-DefaultConfigRBB-debug`` in the second.
173+
174+
Compiling and executing a custom program using the emulator
175+
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
176+
177+
We suppose that ``helloworld`` is our program. You can use ``crt.S``, ``syscalls.c``, and the linker script
178+
``test.ld`` to construct your own program (see
179+
`riscv-tests <https://github.com/riscv/riscv-tests>`_). Note that ``test.ld`` loads the program at
180+
``0x80000000`` so you will need ``-mcmodel=medany`` (see
181+
`The RISC-V Code Models <https://www.sifive.com/blog/2017/09/11/all-aboard-part-4-risc-v-code-models/>`_).
182+
183+
.. code-block:: c
184+
185+
char text[] = "Vafgehpgvba frgf jnag gb or serr!";
144186
145-
make CONFIG=RocketConfig
187+
// Don't use the stack, because sp isn't set up.
188+
volatile int wait = 1;
146189
147-
2. Run the simulation with remote bit-bang enabled. Since we hope to load/run the binary using JTAG,
148-
we can pass ``none`` as a binary (prevents FESVR from loading the program). (Adapted from: https://github.com/chipsalliance/rocket-chip#3-launch-the-emulator)
190+
int main()
191+
{
192+
while (wait)
193+
;
194+
195+
// Doesn't actually go on the stack, because there are lots of GPRs.
196+
int i = 0;
197+
while (text[i]) {
198+
char lower = text[i] | 32;
199+
if (lower >= 'a' && lower <= 'm')
200+
text[i] += 13;
201+
else if (lower > 'm' && lower <= 'z')
202+
text[i] -= 13;
203+
i++;
204+
}
205+
206+
while (!wait)
207+
;
208+
}
209+
210+
First, we can test if this program executes correctly in the simpler emulator (non-RBB) before debugging:
149211

150212
.. code-block:: bash
151213
152-
# note: this uses Chipyard make invocation to run the simulation to properly wrap the simulation args
153-
make CONFIG=RocketConfig BINARY=none SIM_FLAGS="+jtag_rbb_enable=1 --rbb-port=9823" run-binary
214+
$ ./emulator-freechips.rocketchip.system-DefaultConfig helloworld
154215
155-
3. `Follow the instructions here to connect to the simulation using OpenOCD + GDB. <https://github.com/chipsalliance/rocket-chip#4-launch-openocd>`__
216+
Additional verbose information (clock cycle, pc, instruction being executed) can be printed:
217+
218+
.. code-block:: bash
219+
220+
$ ./emulator-freechips.rocketchip.system-DefaultConfig +verbose helloworld 2>&1 | spike-dasm
221+
222+
VCD output files can be obtained using the ``-debug`` version of the emulator and are specified using ``-v`` or
223+
``--vcd=FILE`` arguments. A detailed log file of all executed instructions can also be obtained from the emulator:
224+
225+
.. code-block:: bash
226+
227+
$ ./emulator-freechips.rocketchip.system-DefaultConfig-debug +verbose -v output.vcd helloworld 2>&1 | spike-dasm > output.log
228+
229+
.. note::
230+
Generated VCD waveforms and execution log files can be very voluminous depending on the size of the .elf file
231+
(i.e. code size + debugging symbols).
156232

157233
.. note::
158-
This section was adapted from the instruction in Rocket Chip and riscv-isa-sim. For more information refer
159-
to that documentation: `Rocket Chip GDB Docs <https://github.com/chipsalliance/rocket-chip#-debugging-with-gdb>`__,
160-
`riscv-isa-sim GDB Docs <https://github.com/riscv/riscv-isa-sim#debugging-with-gdb>`__
234+
The time it takes the emulator to load your program depends on executable size. Stripping the .elf executable
235+
will unsurprisingly make it run faster. For this you can use ``$RISCV/bin/riscv64-unknown-elf-strip`` tool to
236+
reduce the size. This is good for accelerating your simulation but not for debugging.
237+
238+
.. warning::
239+
The HTIF communication interface between our system and the emulator relies on ``tohost`` and ``fromhost``
240+
symbols to communicate. If you try to run a totally stripped executable on the emulator, you may get:
241+
242+
.. code-block:: text
243+
244+
$ ./emulator-freechips.rocketchip.system-DefaultConfig totally-stripped-helloworld
245+
This emulator compiled with JTAG Remote Bitbang client. To enable, use +jtag_rbb_enable=1.
246+
Listening on port 46529
247+
warning: tohost and fromhost symbols not in ELF; can't communicate with target
248+
249+
To resolve this, we need to strip all the .elf executable but keep ``tohost`` and ``fromhost`` symbols:
250+
251+
.. code-block:: bash
252+
253+
$ riscv64-unknown-elf-strip -s -Kfromhost -Ktohost helloworld
254+
255+
More details on the GNU strip tool can be found
256+
`here <https://www.thegeekstuff.com/2012/09/strip-command-examples/>`_.
257+
258+
The interest of this step is to make sure your program executes well. To perform debugging you need the original
259+
unstripped version, as explained in the following steps.
260+
261+
Launch the emulator
262+
+++++++++++++++++++++++++
263+
264+
First, do not forget to compile your program with ``-g -Og`` flags to provide debugging support.
265+
266+
We can then launch the Remote Bit-Bang enabled emulator with:
267+
268+
.. code-block:: bash
269+
270+
./emulator-freechips.rocketchip.system-DefaultConfigRBB +jtag_rbb_enable=1 --rbb-port=9823 helloworld
271+
272+
.. note::
273+
You can also use the ``emulator-freechips.rocketchip.system-DefaultConfigRBB-debug`` version instead if you
274+
would like to generate VCD waveforms.
275+
276+
.. note::
277+
If the argument ``--rbb-port`` is not passed, a default free TCP port on your computer will be chosen randomly.
278+
279+
.. note::
280+
When debugging with GDB, the .elf file is not actually loaded by the FESVR. In contrast with Spike, it must
281+
be loaded from GDB as explained in step 5. So the ``helloworld`` argument may be replaced by any dummy name.
282+
283+
Launch OpenOCD
284+
++++++++++++++++++++
285+
286+
You will need a RISC-V Enabled OpenOCD binary. This is installed with rocket-tools in ``$(RISCV)/bin/openocd``,
287+
or can be compiled manually from riscv-openocd. OpenOCD requires a configuration file, in which we define the RBB
288+
port we will use, which is in our case ``9823``.
289+
290+
.. code-block:: tcl
291+
292+
interface remote_bitbang
293+
remote_bitbang_host localhost
294+
remote_bitbang_port 9823
295+
296+
set _CHIPNAME riscv
297+
jtag newtap $_CHIPNAME cpu -irlen 5
298+
299+
set _TARGETNAME $_CHIPNAME.cpu
300+
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
301+
302+
gdb_report_data_abort enable
303+
304+
init
305+
halt
306+
307+
Then we launch OpenOCD in another terminal using the command:
308+
309+
.. code-block:: bash
310+
311+
$(RISCV)/bin/openocd -f ./cemulator.cfg
312+
313+
.. note::
314+
A ``-d`` flag can be added to the command to show further debug information.
315+
316+
Launch GDB
317+
+++++++++++++++++
318+
319+
In another terminal launch GDB and point to the elf file you would like to load then run it with the debugger
320+
(in this example, ``helloworld``):
321+
322+
.. code-block:: bash
323+
324+
$ riscv64-unknown-elf-gdb helloworld
325+
326+
Compared to Spike, the C Emulator is very slow, so several problems may be encountered due to timeouts between
327+
issuing commands and response from the emulator. To solve this problem, we increase the timeout with the GDB
328+
``set remotetimeout`` command.
329+
330+
After that we load our program by performing a ``load`` command. This automatically sets the ``$PC`` to the
331+
``_start`` symbol in our .elf file:
332+
333+
.. code-block:: none
334+
335+
(gdb) set remotetimeout 2000
336+
(gdb) target remote localhost:3333
337+
Remote debugging using localhost:3333
338+
0x0000000000010050 in ?? ()
339+
(gdb) load
340+
Loading section .text.init, size 0x2cc lma 0x80000000
341+
Loading section .tohost, size 0x48 lma 0x80001000
342+
Loading section .text, size 0x98c lma 0x80001048
343+
Loading section .rodata, size 0x158 lma 0x800019d4
344+
Loading section .rodata.str1.8, size 0x20 lma 0x80001b30
345+
Loading section .data, size 0x22 lma 0x80001b50
346+
Loading section .sdata, size 0x4 lma 0x80001b74
347+
Start address 0x80000000, load size 3646
348+
Transfer rate: 40 bytes/sec, 520 bytes/write.
349+
350+
Now we can proceed as with Spike, debugging works in a similar way:
351+
352+
.. code-block:: none
353+
354+
(gdb) print wait
355+
$1 = 1
356+
(gdb) print wait=0
357+
$2 = 0
358+
(gdb) print text
359+
$3 = "Vafgehpgvba frgf jnag gb or serr!"
360+
(gdb) c
361+
Continuing.
362+
363+
^C
364+
Program received signal SIGINT, Interrupt.
365+
main (argc=0, argv=<optimized out>) at src/main.c:33
366+
33 while (!wait)
367+
(gdb) print wait
368+
$4 = 0
369+
(gdb) print text
370+
$5 = "Instruction sets want to be free!"
371+
372+
For more information on GDB debugging, refer to:
373+
374+
* `GDB User Manual <https://sourceware.org/gdb/onlinedocs/gdb/>`_
375+
* `GDB Remote Debugging <https://sourceware.org/gdb/onlinedocs/gdb/Remote-Debugging.html#Remote-Debugging>`_
161376

162377
Example Test Chip Bringup Communication
163378
---------------------------------------

docs/conf.py

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import os
2424
import subprocess
25+
import sys
2526

2627
# -- General configuration ------------------------------------------------
2728

@@ -93,6 +94,57 @@ def get_git_branch_name():
9394
else:
9495
return None
9596

97+
def get_git_remote_url():
98+
"""Get the remote URL of the git repository.
99+
100+
Returns:
101+
str: The HTTPS GitHub URL of the repository, or None if not found/not a GitHub repo.
102+
"""
103+
try:
104+
# Try to get the 'origin' remote URL first
105+
process = subprocess.Popen(["git", "config", "--get", "remote.origin.url"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
106+
url = process.communicate()[0].decode("utf-8").strip()
107+
108+
# If origin doesn't exist, try to get any remote
109+
if process.returncode != 0 or not url:
110+
process = subprocess.Popen(["git", "remote", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
111+
remotes = process.communicate()[0].decode("utf-8").strip()
112+
if remotes:
113+
# Take the first remote found
114+
first_remote = remotes.split('\n')[0]
115+
remote_name = first_remote.split()[0]
116+
process = subprocess.Popen(["git", "config", "--get", f"remote.{remote_name}.url"], stdout=subprocess.PIPE)
117+
url = process.communicate()[0].decode("utf-8").strip()
118+
119+
# Convert SSH URL to HTTPS URL if necessary
120+
if url.startswith("git@github.com:"):
121+
# Transform git@github.com:username/repo.git to https://github.com/username/repo
122+
url = "https://github.com/" + url[15:]
123+
124+
# Remove .git suffix if present
125+
if url.endswith(".git"):
126+
url = url[:-4]
127+
128+
# Handle other GitHub URL formats
129+
if "github.com" in url:
130+
# Extract username/repo part for any GitHub URL format
131+
if "https://github.com/" in url:
132+
repo_path = url.split("https://github.com/")[1]
133+
elif "http://github.com/" in url:
134+
repo_path = url.split("http://github.com/")[1]
135+
else:
136+
# For other GitHub URL formats
137+
return None
138+
139+
# Handle potential trailing slashes
140+
repo_path = repo_path.strip("/")
141+
return f"https://github.com/{repo_path}"
142+
143+
return None
144+
except Exception as e:
145+
print(f"Error getting git remote URL: {e}")
146+
return None
147+
96148
# Come up with a short version string for the build. This is doing a bunch of lifting:
97149
# - format doc text that self-references its version (see title page). This may be used in an ad-hoc
98150
# way to produce references to things like ScalaDoc, etc...
@@ -300,30 +352,42 @@ def gh_file_ref_role(name, rawtext, text, lineno, inliner, options={}, content=[
300352
:gh-file-ref:`my/path`
301353
302354
Produces a hyperlink with the text "my/path" that refers the url:
303-
https://www.github.com/ucb-bar/chipyard/blob/<version>/my/path.
355+
https://www.github.com/repo-owner/repo/blob/<version>/my/path.
304356
305357
Where version is the same as would be substituted by using |version| in
306358
html text, and is resolved in conf.py.
307-
308-
This is based off custom role sphinx plugins like
309-
https://github.com/tdi/sphinxcontrib-manpage. I've inlined this here for
310-
now, but we could just as well make it a module and register it under
311-
`extensions` in conf.py
312359
"""
313360

314361
import docutils
315362
import requests
316363

317-
# Note GitHub permits referring to a tree as a 'blob' in these URLs without returning a 404.
318-
# So I've unconditionally chosen to use blob.
319-
url = f"https://www.github.com/ucb-bar/chipyard/blob/{version}/{text}"
364+
# Get the repository URL dynamically
365+
repo_url = get_git_remote_url()
366+
367+
# Default fallback for safety
368+
if not repo_url:
369+
repo_url = "https://github.com/ucb-bar/chipyard"
370+
print(f"Warning: Could not determine repository URL, using default: {repo_url}")
371+
372+
url = f"{repo_url}/blob/{version}/{text}"
320373

321374
print(f"Testing GitHub URL {url} exists...")
322-
status_code = requests.get(url).status_code
323-
if status_code != 200:
324-
message = f"[Line {lineno}] :{name}:`{text}` produces URL {url} returning status code {status_code}. " \
325-
"Ensure your path is correct and all commits that may have moved or renamed files have been pushed to github.com."
326-
print(message)
375+
try:
376+
status_code = requests.get(url).status_code
377+
if status_code != 200:
378+
message = f"[Line {lineno}] :{name}:`{text}` produces URL {url} returning status code {status_code}. " \
379+
"Ensure your path is correct and all commits that may have moved or renamed files have been pushed to github.com."
380+
print(message)
381+
sys.exit(1) # Exit with error in all environments to catch dead links
382+
except requests.exceptions.ConnectionError as e:
383+
print(f"Warning: Network error when verifying URL {url}: {e}")
384+
print("If you're working offline, you can set the SKIP_URL_CHECK=1 environment variable to bypass URL checks.")
385+
if os.environ.get("SKIP_URL_CHECK") != "1":
386+
sys.exit(1)
387+
else:
388+
print("Continuing build with SKIP_URL_CHECK=1")
389+
except Exception as e:
390+
print(f"Warning: Failed to verify URL {url}: {e}")
327391
sys.exit(1)
328392

329393
docutils.parsers.rst.roles.set_classes(options)

0 commit comments

Comments
 (0)