Introduction to Computer Architecture
CSE 141; Fall 2026
Processor Project · Week 3 of 11
Single-cycle cpu_core
Released · Due
Provided
Given to you
- A skeleton
cpu_core. - Test programs with expected output.
Not given
- More test programs, which your core is also graded on.
Due from you
Graded for correctness
- Implementation for
cpu_core. - A test program of your own.
Graded for feedback
- Milestone Report.
- Detailed block diagram for
cpu_core, revised to match what you built.
Overview
This week you build your first complete processor: a single-cycle core that runs real ARMv6-M (Thumb) machine code. Each instruction is fetched, decoded, executed, and finished in one clock cycle, so the only state in the core is the PC, the register file, and memory.
This core is the foundation for the rest of the quarter. In weeks 4 and 5 you will pipeline it, and later weeks add a memory bus, a peripheral, exceptions, and a cache. Time spent now on clean module boundaries and readable code will pay off every week after this one.
- More open-ended Verilog / multi-module work
- Demonstrate mastery of v6m ISA
- Demonstrate mastery of single cycle concepts
Specification
Your core must correctly execute every week 3 encoding in the instruction scope table (levels 0 through 4).
The starter repository's rtl/cpu_core.sv defines the module
interface for your core. You will implement this core and all of the
modules inside it.
We provide a simplified memory for your core to access, a top-level module that ties these together, an array of test programs, and a testbench that loads code and drives your core.
Your core has two “atypical” output signals to facilitate testing and simulation:
pc_o– Wire your PC register directly to this signal, it should report what instruction is executing this cycle.halt_o– Assert this when you want your core to stop executing.- There is no ISA instruction for “shutdown my computer,” but it is nice to have a way to indicate when a test program ran to completion.
- If your core ever tries to execute the instruction
16'hDE_00, it should asserthalt_o. - The starter template handles this case for you already. For a single-cycle machine, it's fine to leave the logic there. You don't need to handle halts in your decoder (yet...).
At the end of the day, how you implement your core is up to you, so long as it adheres to the ISA contract. For the moment, we are following the ARM ARM for instruction behavior, but otherwise simplifying a lot of things. More precisely, our testbenches assert that a correct core:
- Sets
pc_oto0x0after reset. - Executes an instruction completely in one cycle.
- Updates “externally visible state” correctly each cycle. We
watch the memory bus (and monitor memory contents) and the PC output.
- We do not “look inside” your core, e.g., to view register contents directly. Our test programs (and yours!) need to be smart about how to use what you can see to evaluate what you want to verify.
- You can (and should) “look inside” your core while developing and debugging. The provided testbench has some example code for testbench access, and the waveform viewer can reach inside modules.
Do not change top_level.sv, simple_mem.sv,
program_tb.sv (apart from the debugging example in it), the
justfile, or anything in scripts/. We test your core
with our copies.
Your test program
Write a test program of your own, assembled by hand into an
.imem file in the same format as the provided ones (the README
describes it), encoding each instruction yourself from section A6.7. Your
program must:
- Have at least 10 instructions.
- Run for at least 1000 cycles, and at most 100,000, before it halts.
- Make at least 50 data memory accesses (loads and stores).
- Use only week 3 instructions, and end with
16'hDE_00 (UDF #0). - Start with a comment that says what it does and what it checks.
- Come with a
.after.dmemgiving what it leaves in data memory.- You should work this out by hand. If you just record your core's output, how will your test evaluated whether your core is correct?
Pick something with a result you can check: summing or copying an array, a checksum, sorting a few values.
For your test program, only data memory is checked, so you don't need to
handwrite a .trace (though, it's probably a good idea for you to
know how many cycles it is supposed to take, and validate that). Saying it
again: Don't generate the expected output by running your core: a test whose
answer comes from your core can’t tell you whether your core is right.
Put the program you intend to submit for grading in a folder named
“submit”, e.g. test_programs/submit/01_sumArray/.
Put exactly one program there: if there is more than one, we grade only the
first, in the order ls lists them, and if there is none, you don't
earn the test program point.
You can (and definitely should) write additional programs to test your core,
just don’t put them in the “submit” directory.
Milestone report
Fill in REPORT.md at the top of your repository. The starter has
a template with the section headings:
- What works, and what doesn't
- Tell us what we should expect when testing your processor. Do you think you pass all the public test cases, or are there some known bugs?
- Big design decisions and major changes to the block diagram
- Anything interesting about the way you laid things out?
- E.g., how are you dealing with the hybrid length encoding, where did you put PC?
- Who did what?
- How did you break up the work, and roughly how long did each major piece take?
Getting Started
- Clone the starter repository and read its README to understand what's in it.
- Run
just test-program level0/00_justHalt. Your core is already passing one test case, great start! - Run
just test-programs. Here's the real picture for the work you have to do. - Revise your block diagram from week 2 into the core you'll actually build, and keep revising it while you work.
- Build the core up one level at a time. Prioritize getting a whole instruction through your core end to end.
- Level 0 needs only the PC, fetch, and enough decode for MOVS (immediate) and NOP.
Testing
The test programs are grouped into the same levels as the instruction scope
table, and a level N program uses only instructions from level N or below.
Within a level, the programs are sorted by difficulty.
just test-programs runs them all; the README lists the other
commands.
The provided programs don't use every instruction or check every case, so
passing all of them doesn't mean your core is correct. Your core is also
graded on test programs you don't have. Test your modules on their own with
testbenches of your own (just test <name>), and use your
own test programs to try things the provided ones don't.
The test programs can't see your registers or flags directly, only the PC and
data memory. Until level 2, a program can only check that your core runs the
right instructions in the right order, and halts in the right place. To see
your registers and flags while debugging, use the example in
program_tb.sv (the README explains).
Submission & Grading
Submit via GitHub.
Grading follows the syllabus's milestone
structure, with 5 public points and 5 private points. This week has many test
programs, so instead of losing a point per failing program, each level counts
as one test: a level earns its point when every program in
that level passes. A program passes when its .trace and
.after.dmem match exactly.
Graded for correctness
[5 pts]Public test programs: 1 point for each of levels 0 through 4.[4 pts]Private test programs: 1 point for each of levels 1 through 4. (There are no private level 0 programs.)[1 pt]Your test program. We run it on our reference core and check it against the requirements above. Your program is the authority on what it does: when grading, we generate our own.after.dmemfrom the reference core rather than trusting the one you wrote, and yours must match it exactly. It must meet the requirements, with a correct.after.dmem, to earn the point.
Levels are scored independently, but a level N program can use any instruction from level N or below, so a bug in an early level can cost you later levels too.
Graded for feedback
- Your milestone report.
- Your block diagram, revised to match the core you built, with every signal's name and width.
Extra credit opportunities
[+1 pt]Your test program exposes a bug in our solution.[+1 pt]Best performance in the class [FPGA area]: your core synthesizes to the fewest FPGA resources. Your core must first be correct to be eligible: it must pass every public and private test program, and so must its synthesized netlist (just test-programs-synthruns the public ones).- Run
just synthesize, then openbuild/synth-fpga/1_synth_stat.json. Underdesign, innum_cells_by_type, add up the LUTs (LUT1throughLUT4), theALUcells, and the flip-flops (everyDFF*entry). Lower is better. - How we measure performance will change in later weeks, as the project gets more real.
- Run
Hints & FAQ
Tip: Add some internal monitoring signals and halt aggressively.
E.g., include an output signal decode_error_o in your decoding
logic. In the top level, assign halt_o = decode_error_o | ...,
and have a waveform view or testbench print that lets you quickly scan to see
what error halted your core.
My core passes every provided program. Am I done?
Not necessarily. Go back through the instruction scope table and ask what each instruction could get wrong that no provided program checks, then write tests for it.
Why aren't my registers checked?
How you build your register file is up to you, so the testbench doesn't look inside it. A test program checks a result by storing it to memory, or (at level 4) by branching on it.
This also reflects what's feasible to watch on real cores via tracing (assuming you have the right $2,000 piece of hardware). Very loosely, ARM's Embedded Trace Macrocell (ETM) allows you to monitor which PC is being executed every cycle, and ARM's Data Watchpoint and Trace (DWT) lets you monitor memory bus transactions.
How do I hand-assemble an instruction?
Find its encoding in section A6.7, fill in the fields, and write the 16-bit
value as two bytes, low byte first: movs r1, #42 is 0x212A, so it
goes in the .imem as 2A 21. Write the assembly in a
// comment next to each one, as the provided programs do.
The simulator prints “sorry: constant selects in always_* processes”. Is that a problem?
No. That one is a known Icarus Verilog limitation and the simulation is still
correct. Other sorry messages are real errors; the README has
details.