Add phase-by-phase tutorial notes
Add a Tutorial tree that mirrors the roadmap from Phase 0 through Linux bring-up. Each phase and subphase gets a short learning note with consistent sections for context, goals, new concepts, mental model, learning tasks, pitfalls, tooling, testing, and references. The tutorial material is intentionally explanatory rather than implementation code. It gives a systems-oriented learner enough FPGA, SystemVerilog, RISC-V, firmware, and Linux bring-up context to approach each roadmap phase without turning the notes into copy-paste RTL.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# FPGA-Core Tutorial
|
||||
|
||||
This tutorial tree follows `ROADMAP.md`. Each phase and subphase has a learning note
|
||||
with the same structure:
|
||||
|
||||
- Context
|
||||
- Goals
|
||||
- New Concepts
|
||||
- How To Think About It
|
||||
- Learning Tasks
|
||||
- Pitfalls
|
||||
- Tooling And Testing
|
||||
- References
|
||||
|
||||
The notes assume strong Linux/systems experience and beginner-to-intermediate FPGA and
|
||||
digital-design experience. They intentionally avoid giving implementation code. The goal
|
||||
is to explain what to learn, what to verify, and what mistakes to avoid.
|
||||
|
||||
## Phase Index
|
||||
|
||||
- [Phase 0 - Architecture Contract](phase-00-architecture-contract/phase-00.md)
|
||||
- [Phase 1 - ALU + M Unit](phase-01-alu-m-unit/phase-01.md)
|
||||
- [Phase 2 - Register File](phase-02-register-file/phase-02.md)
|
||||
- [Phase 3 - Decoder](phase-03-decoder/phase-03.md)
|
||||
- [Phase 4 - First CPU](phase-04-first-cpu/phase-04.md)
|
||||
- [Phase 5 - Branches And Jumps](phase-05-control-flow/phase-05.md)
|
||||
- [Phase 6 - Load/Store](phase-06-load-store/phase-06.md)
|
||||
- [Phase 7 - Memory-Mapped UART](phase-07-uart-mmio/phase-07.md)
|
||||
- [Phase 8 - GCC Toolchain Integration](phase-08-gcc-toolchain/phase-08.md)
|
||||
- [Phase 9 - CSRs + M-Mode Traps](phase-09-csrs-traps/phase-09.md)
|
||||
- [Phase 10 - Timer](phase-10-timer/phase-10.md)
|
||||
- [Phase 11 - Interrupt Controller](phase-11-interrupt-controller/phase-11.md)
|
||||
- [Phase 12 - Atomics](phase-12-atomics/phase-12.md)
|
||||
- [Phase 13 - Pipeline](phase-13-pipeline/phase-13.md)
|
||||
- [Phase 14 - SPI Flash + DRAM](phase-14-flash-dram/phase-14.md)
|
||||
- [Phase 15 - M/S/U + Sv32](phase-15-privilege-sv32/phase-15.md)
|
||||
- [Phase 16 - Linux Boot Contract](phase-16-linux-boot-contract/phase-16.md)
|
||||
- [Phase 17 - Linux](phase-17-linux/phase-17.md)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 0.1 - SystemVerilog Package
|
||||
|
||||
## Context
|
||||
|
||||
The package is the shared type layer for the project. It prevents every module from
|
||||
inventing its own encodings and signal names.
|
||||
|
||||
## Goals
|
||||
|
||||
- Define enums for ALU operations, branch types, memory sizes, and instruction classes.
|
||||
- Define structs for stage outputs and memory request/response payloads.
|
||||
- Make later extension possible without rewriting every module boundary.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Packed struct: a struct with a defined bit layout, suitable for wires and registers.
|
||||
- Typedef: named type alias used to make RTL readable.
|
||||
- Import: SystemVerilog mechanism for using package definitions in modules.
|
||||
- Encoding: the bit pattern used for enum values in synthesized hardware.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
The package is not a dumping ground. It should contain stable contracts and shared
|
||||
definitions. If a signal is private to one module, keep it private.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- List every planned stage boundary and define what information must cross it.
|
||||
- Separate architectural information, such as register addresses, from local control.
|
||||
- Decide naming conventions before writing dependent modules.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Relying on implicit enum widths and later discovering mismatched synthesis behavior.
|
||||
- Putting handshake `ready` signals inside one-way payload structs.
|
||||
- Adding fields "just in case" without knowing who drives or consumes them.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Compile the package alone and then with one tiny importing module.
|
||||
- Inspect elaboration messages; type errors here are usually design-contract errors.
|
||||
- Keep comments on fields short but precise about direction and ownership.
|
||||
|
||||
## References
|
||||
|
||||
- SystemVerilog packages and typedefs: https://verificationguide.com/systemverilog/systemverilog-package/
|
||||
- LowRISC SystemVerilog style: https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md
|
||||
- RISC-V unprivileged ISA: https://riscv.org/technical/specifications/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 0.2 - Block Diagram
|
||||
|
||||
## Context
|
||||
|
||||
The block diagram is the visual source of truth for the datapath. It should show what
|
||||
talks to what, what is registered, and what can stall.
|
||||
|
||||
## Goals
|
||||
|
||||
- Draw fetch, decode, register file, ALU, M unit, memory/LSU, and writeback.
|
||||
- Label stage-boundary structs from the package.
|
||||
- Mark clocked state, combinational paths, handshakes, and reset behavior.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Datapath: the route operands and results take through the CPU.
|
||||
- Control path: signals that choose operations, mux inputs, stalls, and traps.
|
||||
- Mux: hardware selector that chooses one of several inputs.
|
||||
- Stall: deliberately holding state while waiting for a multi-cycle event.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Draw movement of information, not just boxes. A useful hardware diagram answers: where is
|
||||
state stored, what changes each clock, what can wait, and what happens on an exception?
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Use arrows for data flow and separate arrows for control when helpful.
|
||||
- Mark every register or memory with a clock edge symbol.
|
||||
- Identify the longest combinational path you expect in early phases.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Drawing BRAM like a zero-latency array.
|
||||
- Forgetting that multi-cycle units need explicit state and stall control.
|
||||
- Hiding reset and trap paths because they are visually inconvenient.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Keep the diagram in `docs/` and update it when interfaces change.
|
||||
- Compare waveform signal names against the diagram after each integration phase.
|
||||
- Use the diagram as a checklist when adding ILA probes.
|
||||
|
||||
## References
|
||||
|
||||
- nand2tetris CPU/data-path intuition: https://www.nand2tetris.org/
|
||||
- RISC-V reader for datapath examples: http://www.riscvbook.com/
|
||||
- Vivado ILA overview: https://docs.xilinx.com/
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Phase 0 - Architecture Contract
|
||||
|
||||
## Context
|
||||
|
||||
Before writing RTL, define the vocabulary of the CPU: stage boundaries, signal bundles,
|
||||
enums, reset behavior, memory map, and debug expectations. This is similar to defining an
|
||||
internal kernel ABI before implementing subsystems.
|
||||
|
||||
## Goals
|
||||
|
||||
- Establish `rv32_pkg.sv` as the shared contract for the design.
|
||||
- Decide what signals cross between fetch, decode, execute, memory, and writeback.
|
||||
- Create a block diagram that can guide implementation and later refactors.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- RTL: register-transfer level description of hardware behavior.
|
||||
- Package: SystemVerilog namespace for shared types, constants, enums, and structs.
|
||||
- Struct: named bundle of related signals; useful for stage outputs and bus payloads.
|
||||
- Enum: symbolic encoding for choices such as ALU operation or branch type.
|
||||
- Combinational logic: output depends only on current inputs.
|
||||
- Sequential logic: output/state changes on a clock edge.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
You are designing interfaces between hardware blocks, not function signatures. Every
|
||||
field becomes wires or registers, so changing it later can ripple through the whole
|
||||
datapath. Bias toward clarity and explicitness, not clever compression.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Sketch the datapath by hand and label every stage boundary.
|
||||
- Write down which signals are data, control, status, or exception metadata.
|
||||
- Decide which fields are needed now and which are placeholders for later phases.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Overfitting the package to Phase 1 and then reworking it repeatedly.
|
||||
- Mixing unrelated control signals into loose wires instead of structured bundles.
|
||||
- Treating the diagram as disposable; it should be updated as the design changes.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use the package in the smallest possible test module to confirm Vivado accepts it.
|
||||
- Keep package dependencies acyclic; shared types should not import implementation modules.
|
||||
- Run syntax checks early, before several modules depend on a broken type.
|
||||
|
||||
## References
|
||||
|
||||
- SystemVerilog IEEE overview: https://www.accellera.org/downloads/standards/systemverilog
|
||||
- LowRISC style guide: https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md
|
||||
- RISC-V specifications: https://riscv.org/technical/specifications/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 1.1 - Combinational ALU
|
||||
|
||||
## Context
|
||||
|
||||
The ALU is the easiest place to learn the difference between software expressions and
|
||||
hardware behavior. Every operation is hardware that exists in parallel behind a selector.
|
||||
|
||||
## Goals
|
||||
|
||||
- Implement RV32I arithmetic, logic, comparison, and shift operations.
|
||||
- Verify every operation independently.
|
||||
- Learn how signedness works in SystemVerilog.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Signedness: whether bits are interpreted as two's-complement signed values.
|
||||
- Shift amount: RISC-V uses only the low 5 bits for RV32 shifts.
|
||||
- Overflow: addition overflow is usually ignored for RV32I arithmetic results.
|
||||
- Combinational completeness: every output is assigned for every input path.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
The same 32-bit vector can be signed or unsigned depending on the operation. Make the
|
||||
interpretation explicit in your design notes and tests.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Make a table of each ALU op, its operands, and expected result semantics.
|
||||
- Hand-check edge cases such as `0`, `-1`, `INT_MIN`, and `INT_MAX`.
|
||||
- Compare arithmetic right shift with logical right shift.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Accidentally creating latches by not assigning outputs in every case.
|
||||
- Using host-language intuition for signed comparisons.
|
||||
- Forgetting that `slt` and `sltu` are different instructions.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use a small self-checking testbench before opening a waveform.
|
||||
- Add waveform inspection for failing tests only; do not debug by staring first.
|
||||
- Include cases where signed and unsigned answers differ.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V unprivileged ISA: https://riscv.org/technical/specifications/
|
||||
- SystemVerilog signed arithmetic notes: https://www.chipverify.com/systemverilog/systemverilog-data-types
|
||||
- Verilator warnings guide: https://verilator.org/guide/latest/warnings.html
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 1.2 - Multi-Cycle M Unit
|
||||
|
||||
## Context
|
||||
|
||||
Multiply and divide belong in a dedicated unit because their timing and control behavior
|
||||
are different from simple ALU operations.
|
||||
|
||||
## Goals
|
||||
|
||||
- Implement multiply, high-half multiply, divide, and remainder behavior.
|
||||
- Learn multi-cycle control using `start`, `busy`, and `done`.
|
||||
- Verify all RISC-V-defined edge cases.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- High-half multiply: result uses bits 63:32 of a 64-bit product.
|
||||
- Iterative divider: divider that computes one bit or small group of bits per cycle.
|
||||
- FSM: finite-state machine controlling multi-cycle behavior.
|
||||
- Latency: number of cycles from request to result.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Treat the M unit like a tiny asynchronous service from the core's perspective. The core
|
||||
asks for work, waits, and resumes only when the result is stable.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Write down the expected result for divide-by-zero and signed overflow.
|
||||
- Draw state transitions for idle, multiply, divide, done, and back-to-back requests.
|
||||
- Decide when inputs are latched and when outputs are valid.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Allowing operands to change while an operation is in flight.
|
||||
- Returning C-language division behavior instead of RISC-V behavior.
|
||||
- Forgetting `mulhsu`, which mixes signed and unsigned operands.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use long waveform windows to inspect division progress.
|
||||
- Include tests that assert `start` while `busy` and confirm the intended behavior.
|
||||
- Synthesize to confirm multiply maps to DSPs and divide maps to logic/state.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V M extension spec: https://riscv.org/technical/specifications/
|
||||
- Project F multiplication: https://projectf.io/posts/multiplication-fpga-dsps/
|
||||
- Project F division: https://projectf.io/posts/division-in-verilog/
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Phase 1.3 - ALU + M Unit On FPGA With VIO
|
||||
|
||||
## Context
|
||||
|
||||
After simulation, place the arithmetic blocks on real hardware and interact with them
|
||||
through Vivado VIO. This teaches the difference between simulated correctness and
|
||||
synthesized hardware behavior.
|
||||
|
||||
## Goals
|
||||
|
||||
- Learn how to instantiate debug IP around simple modules.
|
||||
- Confirm arithmetic blocks synthesize and meet the 50 MHz target.
|
||||
- Practice observing handshake behavior in hardware.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- VIO: Vivado Virtual Input/Output IP, controlled over JTAG.
|
||||
- JTAG: debug/control link used by Vivado Hardware Manager.
|
||||
- Timing closure: proving signals settle before the next clock edge.
|
||||
- Synthesis: compiling RTL into FPGA primitives.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
This is not about exhaustive validation. It is about learning the FPGA tool loop:
|
||||
synthesize, implement, program, poke signals, observe results, and read timing reports.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Identify which signals are useful to drive and which are useful to observe.
|
||||
- Read the timing summary and locate the worst path.
|
||||
- Compare VIO observations with simulator waveforms.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Letting Vivado optimize away debug signals before VIO/ILA can observe them.
|
||||
- Confusing VIO interaction latency with the design's actual clock-cycle behavior.
|
||||
- Ignoring timing warnings because the manual test "seemed to work."
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use VIO for low-bandwidth manual checks, not automated compliance.
|
||||
- Keep a short lab notebook: bitstream, clock, operands, expected, observed.
|
||||
- Learn where Vivado reports resource use for LUTs, FFs, DSPs, and BRAMs.
|
||||
|
||||
## References
|
||||
|
||||
- Vivado Design Suite documentation: https://docs.xilinx.com/
|
||||
- Vivado debug IP overview: https://docs.xilinx.com/
|
||||
- Digilent Arty A7 reference: https://digilent.com/reference/programmable-logic/arty-a7/reference-manual
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 1 - ALU + M Unit
|
||||
|
||||
## Context
|
||||
|
||||
This phase builds the arithmetic core of the CPU. The ALU handles simple single-cycle
|
||||
operations; the M unit handles multiplication and division with an explicit handshake.
|
||||
|
||||
## Goals
|
||||
|
||||
- Build and verify a combinational RV32I ALU.
|
||||
- Build and verify a multi-cycle RV32M unit.
|
||||
- Learn simulation-first hardware development before system integration.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- ALU: arithmetic logic unit; performs integer arithmetic, comparisons, shifts, and logic.
|
||||
- DSP slice: FPGA hard block optimized for multiplication and arithmetic.
|
||||
- Multi-cycle unit: block that takes several clocks to produce a result.
|
||||
- Handshake: protocol using signals such as start, busy, done, valid, or ready.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
The ALU is pure combinational decision logic. The M unit is a small protocol machine. Do
|
||||
not force division into the ALU just because it is mathematically an arithmetic operation.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Understand signed versus unsigned interpretation of the same 32 bits.
|
||||
- Work through RISC-V division edge cases by hand.
|
||||
- Draw the M-unit state machine before writing RTL.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Implementing a combinational divider and then fighting timing forever.
|
||||
- Treating signed comparisons as unsigned or vice versa.
|
||||
- Dropping `done` or accepting a new `start` at the wrong time.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Start with small deterministic test vectors, then add randomized checks.
|
||||
- Use waveform inspection to confirm handshake timing, not only final results.
|
||||
- Synthesize early to see whether multiply infers DSP resources.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V unprivileged ISA, integer and M extension chapters: https://riscv.org/technical/specifications/
|
||||
- AMD/Xilinx DSP48 documentation: https://docs.xilinx.com/
|
||||
- Project F FPGA arithmetic articles: https://projectf.io/posts/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 2.1 - Register File Module
|
||||
|
||||
## Context
|
||||
|
||||
This subphase turns the architectural register model into clocked hardware with two
|
||||
combinational reads and one synchronous write.
|
||||
|
||||
## Goals
|
||||
|
||||
- Implement 32 registers of 32 bits.
|
||||
- Enforce `x0` semantics.
|
||||
- Verify read, write, and collision behavior.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Synchronous write: state changes only at the active clock edge.
|
||||
- Combinational read: output changes when the address changes.
|
||||
- Read-after-write: reading an address in the same cycle it is written.
|
||||
- Bypass/forwarding: returning newly written data without waiting for storage update.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Choose behavior deliberately. A simple non-pipelined core can tolerate many choices, but
|
||||
tests and later pipeline work become easier if the behavior is documented.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Write a truth table for `write_enable`, `rd`, `rs1`, and `rs2` interactions.
|
||||
- Decide how reset affects registers, if at all.
|
||||
- Confirm whether the register file should initialize to zero for simulation clarity.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Resetting a large register file unnecessarily in hardware.
|
||||
- Making simulation behavior differ from synthesis behavior.
|
||||
- Forgetting that reads from `x0` must ignore any stored value.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use assertions for `x0` if your simulator supports them.
|
||||
- Run repeated randomized write/read sequences.
|
||||
- Inspect synthesis resource use; this may infer LUT RAM or flip-flops.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V ABI register names: https://riscv.org/technical/specifications/
|
||||
- SystemVerilog always_ff guidance: https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md
|
||||
- Vivado synthesis guide: https://docs.xilinx.com/
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Phase 2.2 - Optional VIO Validation
|
||||
|
||||
## Context
|
||||
|
||||
The register file is simple enough that simulation is usually sufficient, but VIO can
|
||||
teach practical debug techniques on real FPGA state.
|
||||
|
||||
## Goals
|
||||
|
||||
- Practice observing and driving register-file signals in hardware.
|
||||
- Confirm reset/write/read expectations after synthesis.
|
||||
- Learn when hardware debug is worth the overhead.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Probe: signal connected to debug IP for observation.
|
||||
- Debug visibility: whether a net survives optimization and can be inspected.
|
||||
- Hardware Manager: Vivado tool for programming and interacting with the FPGA.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Use VIO here only as a learning exercise. Do not make it a habit to replace systematic
|
||||
simulation with manual poking.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Select a minimal set of probes: addresses, write data, write enable, read outputs.
|
||||
- Compare one same-cycle read/write case with the simulator.
|
||||
- Observe how a clocked write appears relative to your VIO actions.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Over-instrumenting and making the debug design harder than the actual module.
|
||||
- Forgetting that manual tests rarely cover corner cases.
|
||||
- Treating VIO as evidence of complete correctness.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Keep the VIO wrapper separate from the synthesizable register-file module.
|
||||
- Remove or isolate debug IP before building later phases.
|
||||
- Check resource utilization with and without debug IP.
|
||||
|
||||
## References
|
||||
|
||||
- Vivado debug documentation: https://docs.xilinx.com/
|
||||
- Digilent Arty A7 reference: https://digilent.com/reference/programmable-logic/arty-a7/reference-manual
|
||||
- LowRISC style guide: https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 2 - Register File
|
||||
|
||||
## Context
|
||||
|
||||
The register file is the CPU's small, fast architectural storage. RISC-V has 32 integer
|
||||
registers, with `x0` permanently reading as zero.
|
||||
|
||||
## Goals
|
||||
|
||||
- Build a 32 x 32-bit register file with two reads and one write.
|
||||
- Define same-cycle read/write behavior intentionally.
|
||||
- Learn how small memories map onto FPGA resources.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Read port: ability to read one register address in a cycle.
|
||||
- Write port: ability to update one register address on a clock edge.
|
||||
- Architectural register: register visible to software.
|
||||
- Hardwired zero: `x0` ignores writes and always returns zero.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
The register file is part storage and part contract with the ISA. Its behavior must be
|
||||
boring, predictable, and heavily tested because every instruction depends on it.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Decide whether same-cycle read-after-write returns old or new data.
|
||||
- Make a register naming table from ABI names to `x` numbers.
|
||||
- Understand why two read ports are enough for RV32I/RV32M.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Accidentally allowing writes to `x0`.
|
||||
- Assuming FPGA block RAM behavior without checking read-port needs.
|
||||
- Leaving same-cycle behavior unspecified until integration exposes it.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Test all boundary addresses: `x0`, `x1`, `x31`.
|
||||
- Test simultaneous reads of two different registers and the same register.
|
||||
- Prefer a waveform check for one read/write collision case.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V register convention: https://riscv.org/technical/specifications/
|
||||
- AMD/Xilinx memory resources: https://docs.xilinx.com/
|
||||
- Project F memory initialization and FPGA memories: https://projectf.io/posts/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 3.1 - Instruction Decoder
|
||||
|
||||
## Context
|
||||
|
||||
This subphase creates the combinational decoder module that turns raw instruction bits
|
||||
into `decode_out_t` fields.
|
||||
|
||||
## Goals
|
||||
|
||||
- Decode source/destination registers, immediates, ALU operation, memory operation, and branch type.
|
||||
- Identify legal versus illegal instructions.
|
||||
- Verify all instruction formats with known encodings.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Sign extension: copying a sign bit into higher bits to preserve signed value.
|
||||
- Zero extension: filling upper bits with zero.
|
||||
- Control signal: a signal that chooses what hardware action occurs.
|
||||
- Decode table: mapping from instruction fields to operation semantics.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Decoder bugs often look like random CPU bugs later. Invest heavily here. If an immediate
|
||||
bit is wrong, the ALU and branch logic can be perfect and the program will still fail.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Create a checklist for each instruction family and expected decode fields.
|
||||
- For each immediate format, manually reconstruct the value from bit positions.
|
||||
- Decide how `fence`, `fence.i`, CSR, `ecall`, and `ebreak` are represented before traps exist.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Copying immediate extraction logic without understanding bit order.
|
||||
- Missing `jalr`'s low-bit clearing rule later in execute/control flow.
|
||||
- Letting default decode outputs accidentally describe a valid NOP.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Generate encodings with the RISC-V assembler, not by hand alone.
|
||||
- Use objdump to verify that your test words are the instructions you think they are.
|
||||
- Add negative tests for illegal encodings.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V unprivileged ISA: https://riscv.org/technical/specifications/
|
||||
- RISC-V opcode repository: https://github.com/riscv/riscv-opcodes
|
||||
- GNU assembler manual: https://sourceware.org/binutils/docs/as/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 3 - Decoder
|
||||
|
||||
## Context
|
||||
|
||||
The decoder translates a 32-bit instruction word into control signals. It is where the
|
||||
binary ISA becomes meaningful hardware intent.
|
||||
|
||||
## Goals
|
||||
|
||||
- Decode RV32I/RV32M instruction fields and immediates.
|
||||
- Produce structured control output for later datapath integration.
|
||||
- Learn to verify against assembler-generated encodings.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Opcode: primary instruction-class field.
|
||||
- funct3/funct7: secondary fields that refine instruction meaning.
|
||||
- Immediate: constant encoded inside an instruction, often split across bits.
|
||||
- Illegal instruction: encoding the core does not implement or that is invalid.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
The decoder is a classifier. It should not perform ALU work; it should describe what
|
||||
work the datapath must perform and which operands/control paths are needed.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw bit layouts for R, I, S, B, U, and J formats.
|
||||
- Hand-decode several assembled instructions.
|
||||
- Decide where illegal instruction detection lives and how it reports failures.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Misplacing B-type and J-type immediate bits.
|
||||
- Forgetting sign extension on immediates.
|
||||
- Treating all unknown encodings as harmless NOPs.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use assembler/objdump as a reference for encodings.
|
||||
- Build tests around every format, not just every mnemonic.
|
||||
- Keep decoder tests independent from ALU or register-file behavior.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V unprivileged ISA instruction formats: https://riscv.org/technical/specifications/
|
||||
- RISC-V opcode map: https://github.com/riscv/riscv-opcodes
|
||||
- GNU binutils RISC-V documentation: https://sourceware.org/binutils/docs/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 4.1 - Fetch + Datapath Integration
|
||||
|
||||
## Context
|
||||
|
||||
This subphase builds the first CPU loop: fetch an instruction, decode it, execute it,
|
||||
write back a result, and advance PC.
|
||||
|
||||
## Goals
|
||||
|
||||
- Integrate instruction BRAM at `0x2000_0000`.
|
||||
- Execute straight-line arithmetic and M-extension operations.
|
||||
- Learn stall control around BRAM and the M unit.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Reset PC: address where execution begins after reset.
|
||||
- FSM state: named control state such as fetch, execute, wait, writeback.
|
||||
- Instruction retirement: point where an instruction's architectural effects are complete.
|
||||
- Stall: holding PC/state while waiting for a dependency.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
The CPU is a protocol participant with its memories and M unit. Correct sequencing matters
|
||||
more than minimizing cycles.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Write a cycle-by-cycle timeline for a simple `addi` instruction.
|
||||
- Write a cycle-by-cycle timeline for a multiply/divide instruction.
|
||||
- Decide what happens on illegal instruction before Phase 9 traps exist.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Double-executing an instruction because PC or state advances at the wrong time.
|
||||
- Starting the M unit repeatedly while waiting for it to finish.
|
||||
- Losing the destination register number while a multi-cycle op is in flight.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use a testbench that stops after a fixed halt condition or maximum cycle count.
|
||||
- Compare final register state and intermediate writeback events.
|
||||
- Inspect PC, instruction, state, register write enable, and writeback data in waveforms.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V unprivileged ISA: https://riscv.org/technical/specifications/
|
||||
- Vivado simulation documentation: https://docs.xilinx.com/
|
||||
- Project F memory initialization: https://projectf.io/posts/initialize-memory-in-verilog/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 4.2 - ILA Verification On FPGA
|
||||
|
||||
## Context
|
||||
|
||||
ILA lets you capture internal FPGA signals during real hardware execution. This is the
|
||||
first serious hardware-debug milestone for the integrated CPU.
|
||||
|
||||
## Goals
|
||||
|
||||
- Learn how to select useful ILA probes.
|
||||
- Capture PC, instruction, writeback, and state transitions.
|
||||
- Compare hardware behavior to simulation.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- ILA: Integrated Logic Analyzer, on-chip waveform capture over JTAG.
|
||||
- Trigger: condition that starts or centers a capture.
|
||||
- Sample depth: number of cycles stored in on-chip debug memory.
|
||||
- Probe preservation: keeping signals from being optimized away.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
ILA is an oscilloscope for internal logic. Use it to answer a specific question, not to
|
||||
record everything. Every extra probe has cost.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Choose a trigger such as PC reaching the last instruction.
|
||||
- Decide which signals prove correct instruction retirement.
|
||||
- Compare one hardware trace with the simulator trace for the same program.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Capturing too few cycles before/after the trigger.
|
||||
- Debugging a different bitstream than the source you are reading.
|
||||
- Assuming ILA changes nothing; debug IP consumes resources and can affect timing.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Archive the program image, bitstream, and source commit used for hardware tests.
|
||||
- Use `dont_touch` sparingly and only on debug-critical nets.
|
||||
- Check timing again after inserting ILA.
|
||||
|
||||
## References
|
||||
|
||||
- Vivado ILA documentation: https://docs.xilinx.com/
|
||||
- Digilent Arty A7 reference: https://digilent.com/reference/programmable-logic/arty-a7/reference-manual
|
||||
- AMD/Xilinx debug methodology: https://docs.xilinx.com/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 4 - First CPU
|
||||
|
||||
## Context
|
||||
|
||||
This phase connects fetch, decode, arithmetic, register-file, and writeback into a minimal
|
||||
non-pipelined CPU. It executes straight-line arithmetic programs from instruction BRAM.
|
||||
|
||||
## Goals
|
||||
|
||||
- Build the first integrated datapath.
|
||||
- Learn how BRAM latency shapes control flow.
|
||||
- Verify architectural state changes instruction by instruction.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- BRAM: block RAM, dedicated on-chip FPGA memory with synchronous access.
|
||||
- PC: program counter, the address of the current or next instruction.
|
||||
- Writeback: stage where a result is committed to the register file.
|
||||
- CPI: cycles per instruction.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
This is not a performance exercise. It is an integration exercise. The goal is to make
|
||||
one instruction at a time retire correctly while respecting real memory latency.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw the fetch/execute FSM.
|
||||
- Decide exactly when PC advances.
|
||||
- Trace a short program and record expected register state after each instruction.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Treating BRAM like a combinational array.
|
||||
- Updating PC while a multi-cycle M operation is still in progress.
|
||||
- Writing back results for instructions that should not write a register.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Start with very small programs and known final register values.
|
||||
- Use simulation waveforms before ILA.
|
||||
- Add a visible "halt" or terminal condition for test programs.
|
||||
|
||||
## References
|
||||
|
||||
- AMD/Xilinx block memory documentation: https://docs.xilinx.com/
|
||||
- RISC-V unprivileged ISA: https://riscv.org/technical/specifications/
|
||||
- Digital design FSM overview: https://www.chipverify.com/verilog/verilog-fsm
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 5.1 - Branch Instructions
|
||||
|
||||
## Context
|
||||
|
||||
Branches compare two registers and conditionally update PC to a PC-relative target. They
|
||||
enable loops and conditionals.
|
||||
|
||||
## Goals
|
||||
|
||||
- Implement `beq`, `bne`, `blt`, `bge`, `bltu`, and `bgeu`.
|
||||
- Learn signed and unsigned branch comparisons.
|
||||
- Verify target calculation and fall-through behavior.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- PC-relative addressing: target is computed from current PC plus immediate.
|
||||
- Branch comparator: dedicated comparison logic for branch conditions.
|
||||
- B-type immediate: split immediate format used by branch instructions.
|
||||
- Taken branch: branch condition true, PC becomes target.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Separate three questions: what is the target, is the condition true, and which PC should
|
||||
be used next? Debugging is easier when those are visible independently.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Decode a B-type immediate by hand.
|
||||
- Build test cases where signed and unsigned comparisons differ.
|
||||
- Trace a loop from initialization through exit.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Forgetting branch offsets are multiples of 2 bytes in the encoding.
|
||||
- Comparing signed values with unsigned operators.
|
||||
- Counting loop iterations incorrectly because PC update timing is unclear.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use assembler-generated branch encodings.
|
||||
- In waveforms, inspect PC, immediate, comparator result, and next PC.
|
||||
- Include backward and forward branch targets.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V unprivileged ISA, branch instructions: https://riscv.org/technical/specifications/
|
||||
- RISC-V assembly manual: https://github.com/riscv-non-isa/riscv-asm-manual
|
||||
- RISC-V opcode data: https://github.com/riscv/riscv-opcodes
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 5.2 - Jump Instructions
|
||||
|
||||
## Context
|
||||
|
||||
Jumps provide unconditional control transfer and function-call mechanics. `jal` is
|
||||
PC-relative; `jalr` jumps through a register plus immediate.
|
||||
|
||||
## Goals
|
||||
|
||||
- Implement `jal` and `jalr`.
|
||||
- Store PC + 4 into the destination register.
|
||||
- Verify call and return sequences.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Return address: address of the instruction after the call.
|
||||
- Indirect jump: target comes from a register rather than only the instruction.
|
||||
- J-type immediate: immediate format used by `jal`.
|
||||
- Calling convention: software agreement for argument, return, and saved registers.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
`jal` and `jalr` are both "write link, then redirect PC." The redirect source differs;
|
||||
the writeback behavior is shared.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Hand-trace a call and return using `ra` (`x1`).
|
||||
- Verify `jalr` target bit 0 clearing.
|
||||
- Learn which registers the RISC-V ABI uses for calls.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Writing the jump target instead of PC + 4 into `rd`.
|
||||
- Mishandling `rd = x0`, which should discard the link.
|
||||
- Forgetting `jalr` is commonly used for returns and function pointers.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Test `jal`, `jalr`, and `jalr x0, ra, 0` return style.
|
||||
- Use objdump to confirm labels assemble to expected offsets.
|
||||
- Inspect register writeback and PC change in the same trace.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V unprivileged ISA, jumps: https://riscv.org/technical/specifications/
|
||||
- RISC-V ELF psABI: https://github.com/riscv-non-isa/riscv-elf-psabi-doc
|
||||
- RISC-V assembly manual: https://github.com/riscv-non-isa/riscv-asm-manual
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 5 - Branches And Jumps
|
||||
|
||||
## Context
|
||||
|
||||
This phase adds control flow. The CPU stops being a straight-line executor and gains
|
||||
loops, conditionals, function calls, and returns.
|
||||
|
||||
## Goals
|
||||
|
||||
- Implement conditional branches.
|
||||
- Implement `jal` and `jalr`.
|
||||
- Learn PC selection and control-transfer testing.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Branch target: destination address for a taken branch.
|
||||
- Fall-through: next sequential PC, usually PC + 4.
|
||||
- Link register: register receiving return address for calls.
|
||||
- Control hazard: later pipeline issue where fetched instructions may be wrong.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Control flow is just PC update logic plus optional register writeback. Keep the
|
||||
comparison, target calculation, and PC selection clearly separated.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Hand-compute branch and jump targets from instruction immediates.
|
||||
- Trace a simple loop and count taken versus not-taken branches.
|
||||
- Understand why `jalr` clears bit 0 of the target address.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Off-by-four errors between current PC and next PC.
|
||||
- Using unsigned comparison for signed branches.
|
||||
- Forgetting that `jal`/`jalr` write PC + 4 to `rd`.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Test both taken and not-taken paths for every branch type.
|
||||
- Use short loops with known iteration counts.
|
||||
- Probe PC source selection in simulation and ILA.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V branch and jump semantics: https://riscv.org/technical/specifications/
|
||||
- RISC-V assembly examples: https://github.com/riscv-non-isa/riscv-asm-manual
|
||||
- Computer architecture control flow overview: https://www.nand2tetris.org/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 6.1 - Word Load/Store
|
||||
|
||||
## Context
|
||||
|
||||
Word loads and stores are the first data-memory operations. They are simpler because
|
||||
addresses must be 4-byte aligned and all byte lanes are active.
|
||||
|
||||
## Goals
|
||||
|
||||
- Add data BRAM.
|
||||
- Issue D-bus requests for `lw` and `sw`.
|
||||
- Learn request/response sequencing for loads.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Word: 32-bit value in RV32.
|
||||
- Aligned access: address is a multiple of access size.
|
||||
- Response latency: time from request acceptance to returned data.
|
||||
- Bus decoder: logic that routes an address to the right memory/peripheral.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
A store is mostly "send address and data." A load is "send address, wait for response,
|
||||
then write back." That waiting must be explicit in the control FSM.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw a timeline for `sw` and `lw`.
|
||||
- Mark when `req_valid`, `req_ready`, `rsp_valid`, and `rsp_ready` are meaningful.
|
||||
- Decide what state is retained while waiting for load data.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Writing back load data before BRAM response is valid.
|
||||
- Advancing PC before a load has completed.
|
||||
- Forgetting to tie unused `amo` and sub-word strobes consistently.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Start with store-then-load of one word.
|
||||
- Add tests for first and last valid data BRAM addresses.
|
||||
- Use waveform checks for request acceptance and response timing.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V unprivileged ISA loads/stores: https://riscv.org/technical/specifications/
|
||||
- Vivado BRAM documentation: https://docs.xilinx.com/
|
||||
- AXI valid/ready concepts: https://developer.arm.com/documentation/ihi0022/latest
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 6.2 - Byte And Halfword Access
|
||||
|
||||
## Context
|
||||
|
||||
Sub-word memory operations make C programs practical. `char`, `short`, strings, and packed
|
||||
data all depend on byte and halfword accesses.
|
||||
|
||||
## Goals
|
||||
|
||||
- Implement `lb`, `lbu`, `lh`, `lhu`, `sb`, and `sh`.
|
||||
- Handle byte lanes and sign/zero extension.
|
||||
- Trap misaligned accesses locally in the LSU.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Sign extension: preserving negative value when widening a smaller signed type.
|
||||
- Zero extension: widening an unsigned value by filling high bits with zero.
|
||||
- Byte enable: control bit selecting which byte lane is written.
|
||||
- Misaligned access: address not divisible by the access size.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Stores are about choosing byte lanes. Loads are about extracting the correct lane and
|
||||
extending it correctly. Misalignment is known before the bus request starts.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Map address low bits to byte lanes.
|
||||
- Work through examples loading `0x80` as signed and unsigned byte.
|
||||
- Decide how halfword alignment is detected.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Applying sign extension before selecting the correct byte/halfword.
|
||||
- Letting a misaligned request reach memory.
|
||||
- Confusing endianness with bit numbering in diagrams.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Test every byte offset for `sb` and `lb/lbu`.
|
||||
- Test halfword offsets 0 and 2 as legal and 1 and 3 as misaligned.
|
||||
- Use memory initialization patterns that make byte order obvious.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V unprivileged ISA load/store chapter: https://riscv.org/technical/specifications/
|
||||
- Endianness overview: https://en.wikipedia.org/wiki/Endianness
|
||||
- Project F memory articles: https://projectf.io/posts/
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Phase 6.3 - ILA On The Memory Bus
|
||||
|
||||
## Context
|
||||
|
||||
Memory bugs often need cycle-level visibility. ILA on the D-bus lets you see requests,
|
||||
responses, byte strobes, and stalls in real hardware.
|
||||
|
||||
## Goals
|
||||
|
||||
- Learn practical bus-level debug with ILA.
|
||||
- Capture load/store transactions.
|
||||
- Compare hardware memory behavior with simulation.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Transaction: one logical memory operation, possibly spanning multiple cycles.
|
||||
- Probe grouping: collecting related signals for readable waveforms.
|
||||
- Trigger condition: event that starts capture.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Probe at protocol boundaries. If the LSU and memory disagree, the bus waveform tells you
|
||||
which side violated the contract.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Choose probes for D-bus request payload, request handshake, response payload, response handshake.
|
||||
- Create one program with predictable stores and loads.
|
||||
- Trigger on a target address or final store.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Capturing only data and not the valid/ready signals.
|
||||
- Forgetting byte strobes when debugging sub-word stores.
|
||||
- Using ILA before simulation has narrowed the failure.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use short programs so captures fit in ILA memory.
|
||||
- Keep memory addresses distinctive and easy to recognize.
|
||||
- Re-run timing after adding ILA probes.
|
||||
|
||||
## References
|
||||
|
||||
- Vivado ILA documentation: https://docs.xilinx.com/
|
||||
- Digilent Arty A7 reference: https://digilent.com/reference/programmable-logic/arty-a7/reference-manual
|
||||
- Wishbone spec for bus-debug vocabulary: https://cdn.opencores.org/downloads/wbspec_b4.pdf
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 6 - Load/Store
|
||||
|
||||
## Context
|
||||
|
||||
Load/store connects the CPU to data memory and later to memory-mapped devices. RISC-V is
|
||||
a load/store architecture: arithmetic works on registers, memory access is explicit.
|
||||
|
||||
## Goals
|
||||
|
||||
- Add data BRAM at `0x8000_0000`.
|
||||
- Define and use the D-bus contract.
|
||||
- Support word, byte, and halfword memory operations.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- LSU: load/store unit, responsible for address generation and data formatting.
|
||||
- D-bus: data-side memory request/response channel.
|
||||
- Byte lane: one of the four bytes in a 32-bit word.
|
||||
- Write strobe: byte-enable mask for sub-word stores.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Memory operations are protocol transactions. Address alignment, byte selection, sign
|
||||
extension, response timing, and error classification all matter.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw how a byte store updates one lane of a 32-bit word.
|
||||
- Decide how the LSU detects misalignment before bus issue.
|
||||
- Trace a load from execute through response and writeback.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Returning unshifted data for byte/halfword loads.
|
||||
- Treating misalignment as a bus error instead of an architectural exception.
|
||||
- Forgetting stores do not write back to the register file.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Test all byte offsets within a word.
|
||||
- Use memory dumps or waveform inspection for store byte lanes.
|
||||
- Add maximum cycle timeouts to catch hung bus handshakes.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V load/store semantics: https://riscv.org/technical/specifications/
|
||||
- AMD/Xilinx block RAM documentation: https://docs.xilinx.com/
|
||||
- Wishbone bus spec for handshake comparison: https://cdn.opencores.org/downloads/wbspec_b4.pdf
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 7.1 - UART TX Module
|
||||
|
||||
## Context
|
||||
|
||||
The TX module serializes bytes into start bit, data bits, and stop bit at a fixed baud
|
||||
rate. It is a small timing-driven FSM.
|
||||
|
||||
## Goals
|
||||
|
||||
- Build a standalone transmitter.
|
||||
- Learn baud-rate timing from the 50 MHz system clock.
|
||||
- Verify busy/send behavior before CPU integration.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Baud rate: symbols per second on the serial line.
|
||||
- Start bit: low bit marking beginning of a frame.
|
||||
- Stop bit: high bit marking end of a frame.
|
||||
- Bit timer: counter that holds each serial bit for the right number of clocks.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
UART TX is a deterministic shift-and-timer machine. The hard part is not data structure;
|
||||
it is exact timing and clean handoff from "send byte" to "busy."
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Compute clocks per bit at 50 MHz and 115200 baud.
|
||||
- Draw TX line waveform for one byte.
|
||||
- Decide when `busy` asserts and deasserts.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Off-by-one errors in the bit timer.
|
||||
- Accepting a new byte before the previous frame has completed.
|
||||
- Forgetting idle line is high.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Simulate one byte and inspect the waveform.
|
||||
- Test back-to-back bytes.
|
||||
- On hardware, send a fixed message before involving the CPU.
|
||||
|
||||
## References
|
||||
|
||||
- Digilent Arty A7 reference: https://digilent.com/reference/programmable-logic/arty-a7/reference-manual
|
||||
- UART framing overview: https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter
|
||||
- Nandland UART tutorial: https://nandland.com/uart-serial-port-module/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 7.2 - UART RX Module
|
||||
|
||||
## Context
|
||||
|
||||
The RX module samples an asynchronous serial input and reconstructs bytes. It must handle
|
||||
input synchronization and sampling near bit centers.
|
||||
|
||||
## Goals
|
||||
|
||||
- Build a standalone receiver.
|
||||
- Learn oversampling and input synchronization.
|
||||
- Produce a received byte plus valid indication.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Asynchronous input: signal not aligned to the FPGA clock.
|
||||
- Synchronizer: flip-flop chain reducing metastability risk.
|
||||
- Metastability: temporary uncertain state when sampling near an input transition.
|
||||
- Oversampling: sampling multiple times per bit period to find stable centers.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
RX is less forgiving than TX because the external signal is not clocked by your FPGA.
|
||||
Respect clock-domain boundary hygiene even for a slow UART pin.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw how a start bit is detected.
|
||||
- Decide the sample point for each data bit.
|
||||
- Define what `valid` means and how it is cleared.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Sampling the RX pin without synchronization.
|
||||
- Sampling too close to bit transitions.
|
||||
- Losing bytes because there is no buffering or clear valid/ready policy.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Simulate with ideal frames and then slightly shifted frames.
|
||||
- Test receiver behavior with back-to-back bytes.
|
||||
- Add a FIFO later if software cannot poll fast enough.
|
||||
|
||||
## References
|
||||
|
||||
- Metastability overview: https://www.01signal.com/verilog-design/clockdomains/crossing-basics/
|
||||
- Nandland UART receiver tutorial: https://nandland.com/uart-serial-port-module/
|
||||
- Digilent Arty A7 reference: https://digilent.com/reference/programmable-logic/arty-a7/reference-manual
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 7.3 - Bus Decoder + MMIO
|
||||
|
||||
## Context
|
||||
|
||||
The D-bus now routes requests to either RAM or UART registers. This is the first small
|
||||
SoC-style memory map.
|
||||
|
||||
## Goals
|
||||
|
||||
- Decode address ranges for RAM and UART.
|
||||
- Implement UART TX, RX, and status registers.
|
||||
- Preserve the D-bus handshake contract.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Address map: assignment of address ranges to memory or devices.
|
||||
- Register side effect: read or write that changes device state.
|
||||
- Unmapped access: address with no valid target, later an access fault.
|
||||
- Peripheral slave: bus endpoint that responds to device register accesses.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
MMIO registers are a hardware/software ABI. Once firmware depends on them, changing
|
||||
semantics becomes painful. Document behavior precisely.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Write a register table with access type and side effects.
|
||||
- Decide read behavior for write-only registers and write behavior for read-only registers.
|
||||
- Trace a store to UART TX through the D-bus decoder.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Having two slaves respond to the same address.
|
||||
- Letting no slave respond and hanging the bus forever.
|
||||
- Making status bits unclear or inverted relative to software expectations.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Unit-test the decoder separately from UART timing.
|
||||
- Use ILA probes on selected slave, request, response, and UART status.
|
||||
- Test unmapped access behavior once trap support exists.
|
||||
|
||||
## References
|
||||
|
||||
- OSDev MMIO overview: https://wiki.osdev.org/Memory_Mapped_Registers_in_C/C%2B%2B
|
||||
- RISC-V privileged spec for access faults later: https://riscv.org/technical/specifications/
|
||||
- Wishbone bus spec: https://cdn.opencores.org/downloads/wbspec_b4.pdf
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Phase 7.4 - Hello World
|
||||
|
||||
## Context
|
||||
|
||||
This is the first user-visible proof that software running on your CPU can communicate
|
||||
with the outside world through MMIO.
|
||||
|
||||
## Goals
|
||||
|
||||
- Run a hand-written assembly program from instruction BRAM.
|
||||
- Poll UART status and write bytes to TX data.
|
||||
- Verify output in a terminal.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Polling: repeatedly reading status until a condition is true.
|
||||
- Firmware: low-level software running directly on the hardware.
|
||||
- Memory-mapped register access: using load/store instructions to control a device.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
This milestone tests the full vertical slice: instruction fetch, decode, ALU, branches,
|
||||
loads/stores, bus decode, UART, board pinout, and terminal settings.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Trace each instruction in the polling loop.
|
||||
- Explain why the program waits for `tx_busy` to clear.
|
||||
- Identify every hardware block involved in printing one character.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Terminal configured for the wrong baud or line settings.
|
||||
- Writing bytes without checking busy status.
|
||||
- Debugging UART first when the actual bug is branch/load/store behavior.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- First verify UART standalone.
|
||||
- In hardware, probe UART status and D-bus writes if the terminal is silent.
|
||||
- Keep the program tiny and deterministic.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V assembly manual: https://github.com/riscv-non-isa/riscv-asm-manual
|
||||
- Digilent Arty A7 reference: https://digilent.com/reference/programmable-logic/arty-a7/reference-manual
|
||||
- UART overview: https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 7 - Memory-Mapped UART
|
||||
|
||||
## Context
|
||||
|
||||
This phase connects software-visible memory addresses to a peripheral. UART becomes the
|
||||
first external communication path driven by CPU instructions.
|
||||
|
||||
## Goals
|
||||
|
||||
- Build standalone TX and RX UART blocks.
|
||||
- Map UART registers into the D-bus address space.
|
||||
- Print a message from software running on the CPU.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- MMIO: memory-mapped I/O; device registers accessed with normal loads/stores.
|
||||
- Address decoder: logic routing addresses to selected slaves.
|
||||
- Status register: read-only register exposing peripheral state.
|
||||
- FIFO: queue buffering bytes between producer and consumer.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
The CPU should not know about UART internals. It issues stores and loads; the bus decoder
|
||||
and peripheral register file translate those into device behavior.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw the UART register map.
|
||||
- Decide what happens if software writes while TX is busy.
|
||||
- Decide when RX data is consumed and status changes.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Making register side effects ambiguous.
|
||||
- Forgetting software must poll status before writes.
|
||||
- Combining peripheral timing with CPU timing too tightly.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Verify UART standalone before MMIO integration.
|
||||
- Use a terminal for end-to-end tests and ILA for bus/peripheral mismatches.
|
||||
- Keep register behavior documented for firmware authors.
|
||||
|
||||
## References
|
||||
|
||||
- Digilent Arty A7 USB-UART notes: https://digilent.com/reference/programmable-logic/arty-a7/reference-manual
|
||||
- RISC-V platform-level interrupt spec for later context: https://github.com/riscv/riscv-plic-spec
|
||||
- OSDev MMIO overview: https://wiki.osdev.org/Memory_Mapped_Registers_in_C/C%2B%2B
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 8.1 - Linker Script + Startup Code
|
||||
|
||||
## Context
|
||||
|
||||
C programs assume a runtime environment. Bare metal provides none unless you create it:
|
||||
memory layout, stack pointer, zeroed globals, and entry point.
|
||||
|
||||
## Goals
|
||||
|
||||
- Place code and read-only data in instruction memory.
|
||||
- Place writable data, BSS, and stack in data memory.
|
||||
- Understand what startup code must do before calling `main`.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Section: named region of an object file, such as `.text` or `.bss`.
|
||||
- Load address: where bytes are stored in the image.
|
||||
- Virtual/runtime address: where code expects them at execution.
|
||||
- Stack pointer: register pointing to top of current stack frame.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
The linker script is the contract between memory hardware and compiled software. If it
|
||||
lies, the CPU may be correct and the program will still fail.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw the memory layout from reset PC through stack top.
|
||||
- Identify which sections need initial contents and which are zero-filled.
|
||||
- Understand why `.bss` must be cleared.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Placing stack where it collides with `.bss` or data.
|
||||
- Forgetting alignment requirements.
|
||||
- Using library code that expects syscalls or a full C runtime.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Inspect ELF sections and symbols with binutils.
|
||||
- Disassemble startup code and confirm the first instructions are supported.
|
||||
- Create a C program with global initialized and uninitialized variables.
|
||||
|
||||
## References
|
||||
|
||||
- GNU ld scripts: https://sourceware.org/binutils/docs/ld/Scripts.html
|
||||
- RISC-V ELF psABI: https://github.com/riscv-non-isa/riscv-elf-psabi-doc
|
||||
- OSDev bare bones concepts: https://wiki.osdev.org/Bare_Bones
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 8.2 - First GCC Program
|
||||
|
||||
## Context
|
||||
|
||||
The first compiled program proves your hardware can execute compiler-generated code, not
|
||||
just carefully hand-authored assembly.
|
||||
|
||||
## Goals
|
||||
|
||||
- Compile a tiny C firmware for `rv32im`.
|
||||
- Convert the ELF into a memory image.
|
||||
- Print through UART MMIO from C.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- `-march`: compiler ISA target string.
|
||||
- `-mabi`: ABI selection, here `ilp32` for 32-bit integer/long/pointer.
|
||||
- objcopy: tool that converts ELF into raw binary or other formats.
|
||||
- Disassembly: readable assembly representation of generated machine code.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Treat the compiler as an external producer of instructions. Your job is to verify that
|
||||
every emitted instruction is implemented or fails loudly.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Compare C source to generated assembly.
|
||||
- Identify every load/store used for UART access.
|
||||
- Confirm no CSR or `fence.i` instructions appear before Phase 9.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Using the wrong `-march` and accidentally generating unsupported instructions.
|
||||
- Forgetting `volatile` on MMIO accesses in firmware.
|
||||
- Assuming the compiler will preserve simple-looking loops exactly.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Always inspect early firmware with objdump.
|
||||
- Build at low optimization first, then compare optimized output later.
|
||||
- Keep firmware small enough to single-step mentally.
|
||||
|
||||
## References
|
||||
|
||||
- GCC RISC-V options: https://gcc.gnu.org/onlinedocs/gcc/RISC-V-Options.html
|
||||
- RISC-V ELF psABI: https://github.com/riscv-non-isa/riscv-elf-psabi-doc
|
||||
- GNU objcopy: https://sourceware.org/binutils/docs/binutils/objcopy.html
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 8.3 - Fill Remaining RV32I Gaps
|
||||
|
||||
## Context
|
||||
|
||||
Compiler output will expose missing instructions and edge cases. This subphase closes
|
||||
the base ISA gaps before moving into traps and privilege.
|
||||
|
||||
## Goals
|
||||
|
||||
- Implement remaining RV32I instructions required by compiler output.
|
||||
- Decode `fence` as safe in a cacheless design.
|
||||
- Make unsupported system instructions halt visibly.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- `fence`: memory-ordering instruction.
|
||||
- `fence.i`: instruction-stream synchronization instruction.
|
||||
- `ecall`: environment call, later a trap.
|
||||
- `ebreak`: breakpoint instruction, later a trap.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Incomplete ISA behavior should be loud. A clean halt with PC and instruction word is far
|
||||
better than silent execution through an unsupported instruction.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Make a list of every RV32I instruction and current support status.
|
||||
- Disassemble increasingly complex C programs and mark new instructions.
|
||||
- Understand why `fence` can be harmless without caches while `fence.i` still needs decode policy.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Treating `ecall` or `ebreak` as NOPs.
|
||||
- Missing uncommon instructions emitted by switch statements or stack setup.
|
||||
- Debugging firmware before checking the disassembly.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Add illegal-instruction tests.
|
||||
- Keep a cycle limit in simulations to catch hangs.
|
||||
- Use compiler output as a test source, not a proof of correctness.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V unprivileged ISA: https://riscv.org/technical/specifications/
|
||||
- GCC RISC-V options: https://gcc.gnu.org/onlinedocs/gcc/RISC-V-Options.html
|
||||
- RISC-V assembly manual: https://github.com/riscv-non-isa/riscv-asm-manual
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 8.4 - Meaningful C Program
|
||||
|
||||
## Context
|
||||
|
||||
After minimal C works, write a program large enough to exercise stack, branches, calls,
|
||||
arrays, strings, and UART interaction.
|
||||
|
||||
## Goals
|
||||
|
||||
- Run an interactive or semi-interactive firmware.
|
||||
- Exercise real C control flow and memory behavior.
|
||||
- Build confidence before adding privilege and traps.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Stack frame: per-call storage for return address, locals, and saved registers.
|
||||
- Recursion: function calling itself, useful for stressing stack behavior.
|
||||
- Jump table: compiler-generated table for some switch statements.
|
||||
- Serial monitor: small firmware command interface over UART.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
This is a system test, not a unit test. Failures can come from CPU, memory, compiler
|
||||
assumptions, or firmware bugs. Use it after smaller tests pass.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Trace one function call through prologue and epilogue.
|
||||
- Observe stack growth and confirm it stays within data memory.
|
||||
- Add one feature at a time to firmware and inspect generated assembly.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Pulling in library functions accidentally.
|
||||
- Overflowing the tiny BRAM-backed stack.
|
||||
- Debugging optimized code before unoptimized code works.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use map files to understand memory usage.
|
||||
- Add firmware-visible test results through UART.
|
||||
- Keep a known-good simple firmware for regression comparisons.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V ELF psABI: https://github.com/riscv-non-isa/riscv-elf-psabi-doc
|
||||
- GNU ld map files: https://sourceware.org/binutils/docs/ld/Options.html
|
||||
- Embedded Artistry bare-metal articles: https://embeddedartistry.com/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 8.5 - riscv-tests Compliance
|
||||
|
||||
## Context
|
||||
|
||||
Official instruction tests catch ISA bugs that ad hoc tests miss. Run them before adding
|
||||
trap, interrupt, and privilege complexity.
|
||||
|
||||
## Goals
|
||||
|
||||
- Build and run `rv32ui-p-*` and `rv32um-p-*`.
|
||||
- Create a simulation harness that loads test programs.
|
||||
- Establish regression coverage for later phases.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Compliance test: focused program checking architectural behavior.
|
||||
- Harness: wrapper that provides memory, stop condition, and pass/fail reporting.
|
||||
- Signature region: memory region where tests record results.
|
||||
- Regression: test suite run repeatedly to catch breakage.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Do not treat compliance tests as optional polish. They are how you avoid stacking new
|
||||
features on top of unknown ISA bugs.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Understand how a riscv-test indicates pass/fail.
|
||||
- Learn how ELF sections map into your BRAM model.
|
||||
- Categorize failures by decode, execute, memory, or control-flow likely cause.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Running tests with the wrong ISA target.
|
||||
- Assuming the test harness memory layout matches your hardware automatically.
|
||||
- Ignoring one failing test because normal firmware seems to work.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Automate running all tests once the harness exists.
|
||||
- Keep failing waveforms short and reproducible.
|
||||
- Re-run the suite after every meaningful datapath change.
|
||||
|
||||
## References
|
||||
|
||||
- riscv-tests repository: https://github.com/riscv-software-src/riscv-tests
|
||||
- RISC-V architectural tests: https://github.com/riscv-non-isa/riscv-arch-test
|
||||
- RISC-V unprivileged ISA: https://riscv.org/technical/specifications/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 8 - GCC Toolchain Integration
|
||||
|
||||
## Context
|
||||
|
||||
This phase moves from hand-written assembly to compiled C. The CPU must now satisfy the
|
||||
expectations of a real compiler, linker, and runtime startup.
|
||||
|
||||
## Goals
|
||||
|
||||
- Create linker script and startup code.
|
||||
- Compile simple C programs for the implemented ISA.
|
||||
- Run RISC-V compliance-style tests.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Linker script: file controlling where sections are placed in memory.
|
||||
- crt0: startup code that prepares stack and globals before `main`.
|
||||
- ELF: executable/linkable file format used by toolchains.
|
||||
- ABI: software binary contract for registers, stack, calls, and data layout.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
GCC is an unforgiving test generator. It will use legal instruction combinations you did
|
||||
not think to test manually. That is exactly why this phase matters.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Understand sections: `.text`, `.rodata`, `.data`, `.bss`, and stack.
|
||||
- Learn how ELF becomes a BRAM initialization image.
|
||||
- Compare disassembly with your implemented instruction list.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Advertising ISA extensions before hardware decodes them.
|
||||
- Forgetting to initialize stack or zero `.bss`.
|
||||
- Assuming "simple C" means "simple instruction stream."
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use objdump for every early firmware image.
|
||||
- Keep `-nostdlib` until you deliberately provide runtime support.
|
||||
- Run compliance tests before adding traps and privilege complexity.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V ELF psABI: https://github.com/riscv-non-isa/riscv-elf-psabi-doc
|
||||
- GNU linker scripts: https://sourceware.org/binutils/docs/ld/Scripts.html
|
||||
- riscv-tests: https://github.com/riscv-software-src/riscv-tests
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# Phase 9 - CSRs + M-Mode Trap Handling
|
||||
|
||||
## Context
|
||||
|
||||
This phase adds architectural control registers and the mechanism for exceptions and
|
||||
interrupts. The CPU stops merely halting on faults and starts transferring control to
|
||||
software handlers.
|
||||
|
||||
## Goals
|
||||
|
||||
- Implement key machine-mode CSRs.
|
||||
- Implement CSR instructions and `mret`.
|
||||
- Add trap entry for illegal instructions, breakpoints, ecalls, and access faults.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- CSR: control and status register, special architectural register accessed by CSR instructions.
|
||||
- Trap: synchronous exception or asynchronous interrupt that redirects execution.
|
||||
- `mepc`: CSR holding PC to return to after a trap.
|
||||
- `mcause`: CSR describing why the trap occurred.
|
||||
- `mtvec`: CSR holding trap handler address.
|
||||
- `mret`: instruction returning from machine-mode trap.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Traps are controlled context switches within the CPU. Hardware records enough state for
|
||||
software to inspect the cause, handle it, and resume or terminate.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw the exact state updates on trap entry.
|
||||
- Write a table of each exception source and its `mcause`.
|
||||
- Understand which PC is saved for each exception type.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Saving the wrong PC in `mepc`.
|
||||
- Updating architectural state for a faulting instruction that should not retire.
|
||||
- Treating all exceptions as illegal instructions.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Create tiny programs that intentionally trigger one exception at a time.
|
||||
- Inspect CSR values after trap entry in simulation.
|
||||
- Test `mret` returning to a known instruction address.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V privileged architecture spec: https://riscv.org/technical/specifications/
|
||||
- RISC-V CSR instruction semantics: https://riscv.org/technical/specifications/
|
||||
- RISC-V educational trap handling notes: https://osblog.stephenmarz.com/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 10 - Timer
|
||||
|
||||
## Context
|
||||
|
||||
A timer provides a predictable interrupt source. Operating systems need timers for
|
||||
scheduling, timeouts, and delays.
|
||||
|
||||
## Goals
|
||||
|
||||
- Implement a free-running machine timer counter.
|
||||
- Implement compare register behavior.
|
||||
- Generate and handle timer interrupts through the trap path.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- `mtime`: monotonically increasing machine timer counter.
|
||||
- `mtimecmp`: compare value that triggers a timer interrupt when reached.
|
||||
- Interrupt: asynchronous trap caused by external or timer event.
|
||||
- Pending bit: CSR bit indicating an interrupt source is waiting.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Timer hardware is simple; correct interrupt integration is the real lesson. You must
|
||||
coordinate compare logic, pending state, enable bits, global interrupt enable, and trap entry.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw how `mtime`, `mtimecmp`, `mip`, and `mie` relate.
|
||||
- Decide how software reads/writes a 64-bit timer from a 32-bit core.
|
||||
- Trace a timer interrupt from compare match to handler entry.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Generating level interrupts that never clear.
|
||||
- Mishandling 64-bit register access from RV32 software.
|
||||
- Taking interrupts while already in an unsafe state.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Simulate with small compare values so tests finish quickly.
|
||||
- Test enabling, disabling, pending, and clearing behavior separately.
|
||||
- Add waveform probes for `mtime`, compare result, pending bit, and trap entry.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V privileged architecture spec: https://riscv.org/technical/specifications/
|
||||
- RISC-V ACLINT specification: https://github.com/riscv-non-isa/riscv-aclint
|
||||
- Linux timekeeping background: https://docs.kernel.org/timers/
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Phase 11 - Interrupt Controller
|
||||
|
||||
## Context
|
||||
|
||||
After the timer, the system needs a way to manage external interrupt sources such as UART
|
||||
RX. A PLIC-like controller arbitrates and presents external interrupts to the CPU.
|
||||
|
||||
## Goals
|
||||
|
||||
- Build a minimal interrupt controller.
|
||||
- Connect UART RX as an interrupt source.
|
||||
- Learn interrupt priority, enable, pending, claim, and complete concepts.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- PLIC: Platform-Level Interrupt Controller used by many RISC-V systems.
|
||||
- Interrupt priority: ordering among pending interrupt sources.
|
||||
- Claim: software reads which interrupt it should service.
|
||||
- Complete: software tells the controller an interrupt has been handled.
|
||||
- Edge/level interrupt: whether an event is a pulse or held condition.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
An interrupt controller is hardware/software coordination. The device requests service,
|
||||
the controller prioritizes it, the CPU traps, and software acknowledges the right places
|
||||
in the right order.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw interrupt flow from UART RX byte to trap handler.
|
||||
- Decide whether UART interrupt is level-sensitive or edge-sensitive.
|
||||
- Understand claim/complete even if your first controller is simplified.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Losing an interrupt event because it is only a one-cycle pulse.
|
||||
- Clearing the device before software can observe why it interrupted.
|
||||
- Taking an interrupt repeatedly because pending state is never cleared.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Start with one interrupt source before adding priority.
|
||||
- Test masked, unmasked, pending, claim, and complete behavior.
|
||||
- Use ILA on interrupt request, pending, CPU external interrupt, and trap entry.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V PLIC specification: https://github.com/riscv/riscv-plic-spec
|
||||
- RISC-V privileged architecture spec: https://riscv.org/technical/specifications/
|
||||
- Linux interrupt concepts: https://docs.kernel.org/core-api/genericirq.html
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 12 - A Extension
|
||||
|
||||
## Context
|
||||
|
||||
The RISC-V A extension adds atomic memory operations. Mainline Linux expects atomics for
|
||||
locking, reference counts, futexes, and synchronization.
|
||||
|
||||
## Goals
|
||||
|
||||
- Implement LR/SC.
|
||||
- Implement AMO read-modify-write instructions.
|
||||
- Run RV32A tests before Linux work.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Atomic operation: memory operation that appears indivisible to other agents.
|
||||
- LR/SC: load-reserved/store-conditional pair.
|
||||
- Reservation: remembered address that allows a later store-conditional to succeed.
|
||||
- AMO: atomic memory operation combining load, operation, and store.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
In a single-hart, single-master system atomics are conceptually simple. The value is in
|
||||
building the right architectural behavior now so the Linux path is realistic later.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Study success and failure cases for `sc.w`.
|
||||
- Decide when the reservation is cleared.
|
||||
- List AMO operations and their signed/unsigned comparison behavior.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Returning the wrong success code from `sc.w`.
|
||||
- Forgetting AMOs return the original memory value.
|
||||
- Assuming single-hart shortcuts will remain valid if DMA or another master is added.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Run `rv32ua` tests from riscv-tests or architectural tests.
|
||||
- Use memory traces to verify read-modify-write ordering.
|
||||
- Test reservation clearing on stores to the reserved address.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V unprivileged ISA, A extension: https://riscv.org/technical/specifications/
|
||||
- riscv-tests: https://github.com/riscv-software-src/riscv-tests
|
||||
- Linux atomic operations documentation: https://docs.kernel.org/core-api/wrappers/atomic_t.html
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Phase 13 - Pipeline
|
||||
|
||||
## Context
|
||||
|
||||
Pipelining overlaps multiple instructions to improve throughput. This is optional for
|
||||
the Linux goal but highly educational for computer architecture.
|
||||
|
||||
## Goals
|
||||
|
||||
- Insert registers between conceptual stages.
|
||||
- Handle data and control hazards.
|
||||
- Re-run regression tests after changing timing structure.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Pipeline stage: portion of instruction work separated by registers.
|
||||
- Hazard: situation where overlapping instructions would produce wrong behavior.
|
||||
- Forwarding: using a result before it reaches the register file.
|
||||
- Flush: discarding wrong-path instructions.
|
||||
- Stall: holding one or more stages until a hazard clears.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
A pipeline is not just adding registers. It changes when values are available and when
|
||||
instructions retire. Correctness depends on explicit hazard handling.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw instruction timelines for dependent arithmetic operations.
|
||||
- Identify where branch decisions occur and what must be flushed.
|
||||
- Decide how multi-cycle M and memory operations interact with the pipeline.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Adding pipeline registers before defining valid/kill/stall behavior.
|
||||
- Forgetting load-use hazards.
|
||||
- Letting exceptions retire out of order.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Keep a non-pipelined core as a conceptual reference.
|
||||
- Run compliance tests before and after each pipeline milestone.
|
||||
- Add trace logging of retired instructions if possible.
|
||||
|
||||
## References
|
||||
|
||||
- Computer Organization and Design RISC-V edition: https://shop.elsevier.com/books/computer-organization-and-design-risc-v-edition/patterson/978-0-12-820331-6
|
||||
- Hazard overview: https://en.wikipedia.org/wiki/Hazard_(computer_architecture)
|
||||
- RISC-V formal interface concepts: https://github.com/SymbioticEDA/riscv-formal
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Phase 14 - SPI Flash Boot + DRAM
|
||||
|
||||
## Context
|
||||
|
||||
BRAM is small and initialized by the FPGA bitstream. A Linux-capable system needs
|
||||
persistent boot storage and much larger RAM, so this phase adds flash boot and DDR3L.
|
||||
|
||||
## Goals
|
||||
|
||||
- Boot from on-board SPI flash.
|
||||
- Integrate DDR3L through the Xilinx MIG IP.
|
||||
- Move executable images into DRAM and run from there.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- SPI flash: serial nonvolatile storage on the board.
|
||||
- DRAM: external dynamic memory requiring a controller and refresh.
|
||||
- MIG: Xilinx Memory Interface Generator IP for DDR memory controllers.
|
||||
- Bootloader: small program that prepares memory and jumps to a larger image.
|
||||
- Arbiter: logic choosing which bus master accesses shared memory.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
This is the first major SoC integration phase. External memory is not a simple array:
|
||||
latency varies, calibration matters, and reset sequencing becomes important.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Understand the Arty A7 memory devices and their address ranges.
|
||||
- Draw boot flow from reset to flash fetch to DRAM copy to jump.
|
||||
- Decide how I-bus and D-bus arbitrate for DRAM.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Assuming DRAM is ready immediately after FPGA reset.
|
||||
- Ignoring MIG clocking and reset requirements.
|
||||
- Mixing boot ROM, flash aperture, and DRAM addresses without a clear map.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Validate DRAM with a standalone memory test before CPU boot.
|
||||
- Use ILA around MIG app interface and bus arbiter.
|
||||
- Keep a tiny BRAM-resident fallback test path while debugging flash/DRAM.
|
||||
|
||||
## References
|
||||
|
||||
- Digilent Arty A7 reference: https://digilent.com/reference/programmable-logic/arty-a7/reference-manual
|
||||
- AMD/Xilinx MIG documentation: https://docs.xilinx.com/
|
||||
- JEDEC DDR background: https://www.jedec.org/standards-documents/focus/memory-module-design
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Phase 15 - S-Mode, U-Mode, Sv32 Virtual Memory
|
||||
|
||||
## Context
|
||||
|
||||
Linux normally runs with privilege separation and virtual memory. This phase adds
|
||||
supervisor/user modes and Sv32 address translation.
|
||||
|
||||
## Goals
|
||||
|
||||
- Add supervisor and user privilege modes.
|
||||
- Implement Sv32 page-table walking.
|
||||
- Add S-mode CSRs and page-fault traps.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Privilege mode: execution level controlling access rights.
|
||||
- S-mode: supervisor mode, where the OS kernel normally runs.
|
||||
- U-mode: user mode, where applications normally run.
|
||||
- MMU: memory management unit performing address translation and protection.
|
||||
- Sv32: RISC-V 32-bit virtual-memory scheme with two-level page tables.
|
||||
- TLB: translation lookaside buffer, cache of recent address translations.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Virtual memory is both translation and permission checking. A correct page walker that
|
||||
ignores permissions is not enough for an OS.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Draw Sv32 virtual address fields, page table levels, and PTE format.
|
||||
- Trace one virtual load through translation to physical memory.
|
||||
- Learn which traps are page faults versus access faults.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Confusing physical memory protection with virtual page permissions.
|
||||
- Mishandling `satp` updates and `sfence.vma`.
|
||||
- Letting user mode access supervisor-only pages.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Start with hand-built page tables and one mapped page.
|
||||
- Test instruction, load, and store page faults separately.
|
||||
- Add trace visibility for virtual address, PTEs, physical address, and cause.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V privileged architecture spec, Sv32: https://riscv.org/technical/specifications/
|
||||
- Linux memory-management docs: https://docs.kernel.org/mm/
|
||||
- OSDev paging overview: https://wiki.osdev.org/Paging
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Phase 16 - Linux Boot Contract
|
||||
|
||||
## Context
|
||||
|
||||
Linux does not start from reset like bare-metal firmware. It expects a specific entry
|
||||
state, memory layout, device tree, and usually firmware services.
|
||||
|
||||
## Goals
|
||||
|
||||
- Define the exact kernel entry ABI.
|
||||
- Provide a device tree describing the SoC.
|
||||
- Choose direct M-mode Linux or OpenSBI + S-mode Linux.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Device tree: data structure describing hardware to the kernel.
|
||||
- DTB: compiled binary form of a device tree source.
|
||||
- SBI: Supervisor Binary Interface, firmware API used by S-mode kernels.
|
||||
- OpenSBI: common RISC-V machine-mode firmware implementation.
|
||||
- Hart: RISC-V hardware thread, roughly a CPU core/thread.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
This phase is about removing ambiguity before kernel bring-up. If the boot contract is
|
||||
wrong, Linux often just hangs early with little output.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Read the RISC-V Linux boot protocol and list required register state.
|
||||
- Draft a device tree matching your memory map and interrupt topology.
|
||||
- Decide whether the first Linux attempt uses direct M-mode or OpenSBI.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Passing the wrong DTB address in `a1`.
|
||||
- Loading the kernel at the wrong alignment.
|
||||
- Claiming device-tree compatibility with hardware you did not implement.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Validate DTS with `dtc`.
|
||||
- Keep early console strategy simple and documented.
|
||||
- Build small firmware checks that print boot parameters before jumping to Linux.
|
||||
|
||||
## References
|
||||
|
||||
- RISC-V Linux boot protocol: https://docs.kernel.org/arch/riscv/boot.html
|
||||
- RISC-V device-tree CPU bindings: https://www.kernel.org/doc/Documentation/devicetree/bindings/riscv/cpus.yaml
|
||||
- OpenSBI: https://github.com/riscv-software-src/opensbi
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Phase 17 - Linux
|
||||
|
||||
## Context
|
||||
|
||||
This is the summit: boot a minimal Linux kernel with a BusyBox initramfs to a shell over
|
||||
UART on the CPU and SoC you built.
|
||||
|
||||
## Goals
|
||||
|
||||
- Build a kernel matching the implemented ISA and platform.
|
||||
- Provide DTB and initramfs.
|
||||
- Reach an interactive shell or a controlled init process.
|
||||
|
||||
## New Concepts
|
||||
|
||||
- Kernel config: set of build-time options selecting architecture and drivers.
|
||||
- Initramfs: initial root filesystem bundled or loaded with the kernel.
|
||||
- BusyBox: compact Unix userland used in embedded systems.
|
||||
- Early console: minimal logging path before normal console drivers initialize.
|
||||
- Root filesystem: filesystem mounted as `/` by Linux.
|
||||
|
||||
## How To Think About It
|
||||
|
||||
Linux bring-up is system debugging under poor visibility. Work from known-good layers:
|
||||
CPU tests, DRAM test, boot firmware, DTB validation, early console, then userspace.
|
||||
|
||||
## Learning Tasks
|
||||
|
||||
- Understand the kernel image format and load addresses for RV32.
|
||||
- Build the smallest kernel config that matches your hardware.
|
||||
- Trace boot log milestones and map them to hardware dependencies.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Debugging Linux before compliance tests and DRAM tests are clean.
|
||||
- Enabling drivers for devices you do not actually implement.
|
||||
- Losing console output because UART binding or clock frequency is wrong.
|
||||
|
||||
## Tooling And Testing
|
||||
|
||||
- Use earlycon/earlyprintk-style mechanisms where applicable.
|
||||
- Keep kernel, DTB, firmware, and bitstream versions tied together.
|
||||
- Save full boot logs and compare against previous attempts.
|
||||
|
||||
## References
|
||||
|
||||
- Linux RISC-V documentation: https://docs.kernel.org/arch/riscv/
|
||||
- Buildroot: https://buildroot.org/
|
||||
- BusyBox: https://busybox.net/
|
||||
|
||||
Reference in New Issue
Block a user