EECS 370 Course Projects
Undergraduate Winter 2026 Disciplinary Depth Course Grade: #
EECS 370: Introduction to Computer Organization bridges the gap between software and hardware by building a complete computing stack from scratch — designing an ISA, writing a toolchain, and simulating the microarchitecture that executes it.
Topic 1: The LC2K Toolchain
This section covers the full software toolchain for the LC2K instruction set architecture, from raw assembly text to linked, executable machine code. By building each layer myself, I developed a concrete understanding of how compilers and operating system loaders prepare programs for execution on real hardware.
Project #1a: LC2K Assembler
- Objective: Translating human-readable LC2K assembly into 32-bit hex machine code, handling all instruction formats and symbolic references.
- Build: Implemented a two-pass assembler in C. The first pass builds a label-to-address lookup table; the second pass encodes R-type (
add,nor), I-type (lw,sw,beq), J-type (jalr), and pseudo-ops (halt,noop,.fill) into their 32-bit binary representations using bitwise packing. - Functionality: Correctly handles PC-relative branch offsets for
beq, validates register ranges [0–7] and 16-bit offset bounds, detects duplicate label definitions, and enforces strict blank-line and unrecognized-opcode error policies.
Project #1s: LC2K Instruction-Level Simulator
- Objective: Simulating the full execution of an LC2K machine code program, modeling the processor's architectural state cycle-by-cycle.
- Build: Implemented a fetch-decode-execute loop in C that maintains a complete architectural state struct — program counter, an 8-register file (
reg[0..7]), and a 65,536-word memory. Each cycle, instruction fields are extracted via bitmasks (e.g.,mask_opcode = 0x1C00000) and dispatched through a switch statement covering all seven opcodes. - Functionality: Correctly implements signed branch arithmetic via 16-bit sign extension (
convertNum), handlesjalrlink-register semantics, and terminates cleanly onhalt, printing full machine state and execution statistics.
Project #2a: Multi-File Object Assembler
- Objective: Extending the single-file assembler to support modular, multi-file compilation by emitting object files — the same format used by real-world compilers like GCC.
- Build: Modified the assembler to separate output into four sections: a Text section (instructions), a Data section (
.filldirectives), a Symbol Table recording global labels as typeT(text),D(data), orU(undefined/external), and a Relocation Table tracking every instruction that references a symbol needing linker patching. - Functionality: Correctly handles all three symbol visibility cases, enforces that instructions must precede
.filldata, and leaves relocatable offsets at zero as placeholders for the linker to resolve.
Project #2l: Multi-File Linker
- Objective: Combining up to five separately-assembled object files into a single flat executable, resolving all cross-file symbol references — mirroring the job of
ldon a real system. - Build: Built a linker in C that: (1) assigns absolute
textStartingLineanddataStartingLineoffsets to each file by concatenating all text sections before all data sections; (2) constructs a Master Symbol Table from all defined global labels, catching duplicate definitions; (3) iterates the relocation tables of every file and patches each instruction's 16-bit offset field with the computed final address, correctly distinguishing global labels, the specialStacksymbol, and local address fixups. - Functionality: Handles error cases including undefined global labels, duplicate global definitions, and misuse of the reserved
Stacklabel inside object files.
Project #2r: Recursive Combination in LC2K Assembly
- Objective: Implementing the binomial combination function $C(n, r) = C(n-1, r) + C(n-1, r-1)$ in pure LC2K assembly, with no hardware stack support — only registers and memory.
- Build: Manually engineered a call stack using
sw/lwwith a stack pointer register (r5). Each recursive call saves the return address, live arguments (n,r), and intermediate results (C(n-1,r)) onto the stack before branching, then restores them on return. Usedjalrfor indirect function dispatch via a pre-loaded function address register. - Functionality: Correctly computes $C(7, 3) = 35$ using two levels of mutual recursion, bottoming out at the base cases $r=0$ and $n=r$, and cleanly returning results in
r3through the full call stack.
Topic 2: Microarchitecture & Memory Hierarchy
With the toolchain complete, I moved down the stack to simulate the hardware itself. This section covers the two major techniques that give modern processors their performance: pipelining, which overlaps instruction execution, and caching, which bridges the speed gap between the CPU and main memory.
Project #3: 5-Stage Pipelined Processor Simulator
- Objective: Simulating a fully-pipelined LC2K processor that executes up to five instructions simultaneously, while correctly handling all data and control hazards.
- Build: Implemented five pipeline stages — IF, ID, EX, MEM, WB — as explicit pipeline register structs (
IFIDType,IDEXType,EXMEMType,MEMWBType,WBENDType). Each cycle, anewStateis computed from the current state and then atomically committed, modeling real clocked flip-flop behavior. - Functionality: Handles three classes of hazards: (1) Load-Use Hazards — detected in the ID stage by comparing the LW destination against incoming source registers; resolved by freezing the PC and IF/ID register for one cycle and injecting a NOOP bubble into ID/EX. (2) Data Hazards — resolved by forwarding results from the EX/MEM, MEM/WB, and WB/END pipeline registers directly into the EX stage ALU inputs, with priority given to the most recent write. (3) Control Hazards — a taken BEQ detected in the EX stage flushes the three in-flight instructions behind it by overwriting IF/ID, ID/EX, and EX/MEM with NOOPs.
Project #4: Set-Associative Cache Simulator
- Objective: Implementing a parameterized, set-associative cache that plugs into the LC2K pipeline simulator, modeling the behavior of a real CPU memory hierarchy.
- Build: Designed a cache simulator in C with fully configurable parameters:
blockSize,numSets, andblocksPerSet. Each cache block stores atag,validbit,dirtybit, and anlruLabelcounter. The address is decomposed as: $\text{tag} = \lfloor \text{addr} / (\text{blockSize} \times \text{numSets}) \rfloor$, $\text{setIndex} = \lfloor \text{addr} / \text{blockSize} \rfloor \bmod \text{numSets}$, $\text{blockOffset} = \text{addr} \bmod \text{blockSize}$. - Functionality: On a hit, updates the LRU counters for the set and services the read or write directly from cache. On a miss, selects the LRU block for eviction — writing it back to memory via
mem_accessonly if it is dirty (cacheToMemory), or discarding it silently if clean (cacheToNowhere) — then loads the new block from memory (memoryToCache) before servicing the original request. Tracks hit count, miss count, and writeback count, and reports dirty blocks remaining at halt.