Tracing the Nested Calls Behind cmd_c

Engineering

Posted by Bruce Lee on 2024-04-08

About Me

Welcome to my blog! This is where I collect my observations and notes on programming and technology. The main subjects range from implementation details to broader ideas about programming.

Main Topics

  • Engineering Projects: Exploring implementation details and how technical systems work.
  • C/C++: Notes on language features and programming techniques.
  • The Programmer’s Perspective: Ideas about developing a career and a way of thinking as a programmer.

For more, visit the categories page.

Contact

If you have questions or would like to discuss something, please get in touch through the About page.

Thank you for reading and for your support. I hope these notes help you on your own technical journey!


Execution Commands Call cpu_exec

Commands such as cmd_c call cpu_exec. Its argument specifies how many instructions this invocation should execute. cpu_exec passes that count to execute(n).

Inside execute

execute calls exec_once, passing a temporary Decode object named s and the current value of cpu.pc. The count n supplied by cpu_exec controls a loop that repeatedly calls exec_once.

What Is Decode?

The structure contains the instruction-address fields pc, snpc, and dnpc, an ISADecodeInfo field named isa, and an optional debugging buffer introduced through IFDEF.

In other words, it holds the addresses needed during execution, the instruction’s binary encoding, and, when enabled, a logbuf for tracing.

What Does ISADecodeInfo Hold?

ISADecodeInfo contains a union named inst, whose uint32_t val member stores the binary encoding of the instruction being executed.

The IFDEF Macro in Decode

1
IFDEF(CONFIG_ITRACE, char logbuf[128]);

When the instruction-tracing configuration macro is enabled, this expands to char logbuf[128];. It belongs to the debugging support, so I set aside the details here.

Inside exec_once

The call exec_once(&s, cpu.pc) supplies the temporary Decode object and the global program-counter value.

The function initializes s->pc and s->snpc from cpu.pc, calls isa_exec_once(s), and finally assigns s->dnpc back to cpu.pc. Thus, after an instruction executes, the global program counter identifies the next instruction. The temporary decoding object carries the intermediate state; its three PC fields are especially important.

The Call to isa_exec_once(s)

The essential implementation is just two lines:

1
2
s->isa.inst.val = inst_fetch(&s->snpc, 4);
return decode_exec(s);

To understand s->isa.inst.val, follow the return value of inst_fetch. It is the binary encoding of the instruction to execute, stored in the instruction field described above.

After fetching the encoding, isa_exec_once calls decode_exec(s) and returns its result.

What Does inst_fetch(&s->snpc, 4) Do?

The pointer supplies the current fetch address. The function calls vaddr_ifetch(*pc, len) to read the instruction bits. Here len is 4 bytes, giving a 32-bit instruction.

It then increments s->snpc by that length, making it the address of the next sequential instruction, and returns the fetched value.

Following decode_exec

The function first declares the src1, src2, and imm operand fields and initializes s->dnpc from s->snpc. This makes sequential execution the default next-PC behavior.

It defines INSTPAT_INST and INSTPAT_MATCH, then uses INSTPAT_START, a sequence of INSTPAT invocations, and INSTPAT_END to match and execute the instruction.

Before returning, it writes zero to R(0), ensuring that the RISC-V x0 register retains its required value.


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !