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!
Overview
init_rand() and Timing
The timing code in timer.c conditionally includes sys/time.h and time.h. The former provides facilities such as timeval and gettimeofday; timeval represents a time using seconds and microseconds.
get_time returns now - boot_time, so its result is an elapsed interval. Facilities declared in time.h are also relevant to measuring time and processor usage. This code is important when evaluating program execution.
init_log
The initialization sequence passes log_file to init_log. monitor.c declares the variable globally:
1 | static char *log_file = NULL; |
The implementation in log.c sets up the logging destination and prints the initialization information. These notes emphasize keeping diagnostics available through standard output when a separate log file is not being used. The precise response to a requested file failing to open should be checked in the implementation; a fallback for an unspecified file is not necessarily the same as a fallback after an open failure.
In AM’s nemu.mk, NEMUFLAGS adds -l and the path to nemu-log.txt.
init_isa
This function copies the default image into guest memory at RESET_VECTOR. guest_to_host translates that guest address into an address in the emulator’s host-allocated memory, backed by the pmem array defined in paddr.c.
memcpy copies the built-in image into that array. It is only a default guest program; a different image can be loaded later.
The function then calls restart, which sets cpu.pc to RESET_VECTOR and initializes cpu.gpr[0] to zero.
load_img
load_img runs after init_isa. When an image filename has been provided, fread replaces the default bytes with the selected program image.
fseek(fp, 0, SEEK_SET) returns to the start of the image file. SEEK_SET denotes an offset relative to the file’s beginning; the related seek constants describe other reference points.
How much should fread read? Its size and count arguments can describe one-byte elements or a single element covering the entire image. Once the file length is known, the latter form is convenient:
1 | fseek(fp, 0, SEEK_END); |
Revisiting parse_args
Seeing getopt_long reminded me of studying the same library earlier. Of its return values, 1 is the one relevant here.
img_file is a global char * in monitor.c. When the parser returns 1 for the non-option image argument in this setup, optarg supplies the filename. load_img later consumes it.
This raises a useful build-system question: how do AM’s makefiles construct those arguments, and how does that differ from building NEMU itself?
Passing the Image Argument From AM
Selecting One Test or All Tests
Compare make ARCH=riscv32-nemu ALL=add run with make ARCH=riscv32-nemu run. The first supplies one test name through ALL. Without that override, the makefile discovers all test programs:
1 | ALL = $(basename $(notdir $(shell find tests/. -name "*.c"))) |
It finds .c files, removes their directory components, and removes their suffixes. The remaining question is how Make builds and executes each test independently without an explicit loop.
Generating a Small Makefile for Each Test
The top-level makefile in cpu-tests uses ALL more cleverly than the simple makefiles I had written before. Rather than retaining complete source paths, it derives each base name:
1 | ALL = $(basename $(notdir $(shell find tests/. -name "*.c"))) |
The all target depends on those names prefixed with Makefile.. Each resulting name identifies a temporary makefile dedicated to one source program.
The pattern rule for Makefile.% depends on the matching source and on latest, which was an empty extension point in the version I examined. Its recipe writes definitions of NAME and SRCS, followed by an include of ${AM_HOME}/Makefile, into the temporary file.
It then invokes Make using that file, records the test result, and deletes the temporary makefile.
The dependency graph explains the apparent loop: all requires every generated Makefile.<test> target, so Make processes the prerequisites and their recipes. In an ordinary serial invocation, one recipe completes before the next begins. This is a dependency-driven traversal, not a command that alternates individual recipe lines between every test. Parallel Make can of course schedule independent targets concurrently.
Each generated makefile provides one NAME and one SRCS value and includes the common AM build rules. The common rules therefore build and execute that test as an independent program. This combination of generated configuration and shared rules is what automates the full test set.
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 !