Skip to main content

Instructions to Hardware: A Gentle Introduction to Computer Architecture, ARM Assembly, and Verilog

A hands-on workshop guide connecting C source code to ARM assembly, machine code, a simplified single-cycle processor, SystemVerilog simulation, and GTKWave inspection.

On this page

This handout is designed to be followed from top to bottom during the workshop and reused later as a laboratory reference. The presentation is the primary source for the sequence, terminology, examples, and commands. Where the software-installation details were checked against current online documentation, those items are marked as web-verified and linked to official project or vendor pages.

The translation path from C source to hardware

Workshop question: How does a statement such as z = x + y; become an electrical operation inside a processor?

Table of Contents


1. Workshop Overview

The workshop connects five views of the same computation:

C source
   ↓
ARM assembly
   ↓
32-bit machine code
   ↓
single-cycle datapath
   ↓
Verilog/SystemVerilog model + simulation waveform

The emphasis is not memorizing ARM encoding or HDL syntax. The goal is to see how software-visible instructions map onto registers, an ALU, memories, control logic, and observable digital signals.

The presentation introduces the layers in this order: source code, ARM assembly, machine code, instruction fields, a simplified processor, Verilog/SystemVerilog, testbenches, Icarus Verilog, and GTKWave. This handout keeps that teaching progression but moves the simple_sum.s → memfile.dat generation immediately before the full-processor simulation so that the laboratory workflow is executable from start to finish.

Suggested live-workshop use: follow the numbered hands-on procedures. Explanatory subsections can be read in more detail after the workshop.

↑ Back to top

2. Learning Objectives and Scope

By the end of the workshop, you should be able to:

  • explain the path C → assembly → machine code → datapath → HDL simulation;
  • recognize the roles of basic AArch32 registers such as r0, r1, r2, sp, lr, and pc;
  • explain the load–operate–store mental model;
  • generate A32 assembly using arm-none-eabi-gcc;
  • inspect machine-code words with arm-none-eabi-objdump;
  • describe and simulate a small combinational ALU;
  • distinguish a design module from a simulation testbench;
  • compile the supplied single-cycle processor and its separate testbench using Icarus Verilog;
  • change simulation length using +CYCLES=N without recompiling;
  • inspect the ARM execution in GTKWave and identify the successful final store.

This is deliberately a gentle introduction. The workshop does not attempt to cover pipelines, hazards, caches, speculative execution, virtual memory, detailed ABI rules, synthesis, place-and-route, or timing closure. Real Arm processors are much more sophisticated than the educational single-cycle model used here.

↑ Back to top

3. Required Software

Recommended platform: Windows 10/11, 64-bit, using the MSYS2 UCRT64 terminal. The presentation uses this Windows-native setup to avoid switching between Windows and WSL during a short laboratory session. MSYS2 documents UCRT64 as its modern Universal C Runtime environment and made it the default over MINGW64; the current package pages provide all tools used here.

3.1 Software and official pages

SoftwarePurpose in this workshopOfficial pageRecommended installation
MSYS2UCRT64 terminal and pacman package managerMSYS2 / InstallerCurrent 64-bit installer; open MSYS2 UCRT64
Native GCCGeneral C compiler used in the workshop toolchainMSYS2 GCC packageUCRT64 package
Arm bare-metal GCCGenerates A32 assembly and assembles simple_sum.sMSYS2 Arm GCC packageUCRT64 package
Icarus VerilogCompiles Verilog/SystemVerilog designs and testbenchesIcarus Verilog documentation / MSYS2 packageUCRT64 package
GTKWaveOpens VCD waveform filesGTKWave / MSYS2 packageUCRT64 package

Web-verified on 2026-08-11: the MSYS2 repository listed Arm GCC 16.1.0-1, Icarus Verilog 13.0-2, and GTKWave 3.3.127-1 in UCRT64. Exact package versions can change; use the current MSYS2 packages rather than pinning these versions.

3.2 Install MSYS2

  1. Download the current 64-bit installer from the official MSYS2 installer page.
  2. Install to the default short path such as C:\msys64, unless your laboratory has a managed-software policy.
  3. From the Windows Start menu, open MSYS2 UCRT64. Do not use the plain MSYS2 MSYS shell for these package commands.

3.3 Update the package database and installed packages

Run in MSYS2 UCRT64:

pacman -Syu

If MSYS2 asks you to close the terminal after updating core components, close all MSYS2 windows, reopen MSYS2 UCRT64, and run:

pacman -Syu

again.

3.4 Install the workshop packages

pacman -S --needed \
  mingw-w64-ucrt-x86_64-gcc \
  mingw-w64-ucrt-x86_64-arm-none-eabi-gcc \
  mingw-w64-ucrt-x86_64-iverilog \
  mingw-w64-ucrt-x86_64-gtkwave

The Arm GCC package also pulls in the corresponding Arm binutils, including arm-none-eabi-objdump. The Icarus package contains both iverilog and vvp.

3.5 Verify the installation

gcc --version
arm-none-eabi-gcc --version
arm-none-eabi-objdump --version
iverilog -V
vvp -V
gtkwave --version

Pass condition: each command prints version information rather than command not found.

3.6 Create a workshop folder

mkdir -p ~/workshop
cd ~/workshop
pwd

You may instead use another writable folder. The important rule for the processor simulation is that arm_single.sv, arm_single_tb.sv, and memfile.dat must be in the working folder when you run the simulation.

↑ Back to top

4. Workshop Files

Before beginning the processor simulation, download the three required processor files and place them in the same working folder.

4.1 Required processor files

FilePurposeDownloadPlace it in
arm_single.svSingle-cycle ARM processor plus instruction and data memoriesDownload arm_single.sv~/workshop
arm_single_tb.svSeparate simulation testbench containing clock/reset generation, +CYCLES=N control, self-checking, and waveform dumpingDownload arm_single_tb.sv~/workshop
memfile.datARM machine-code program loaded by the processor’s instruction memoryGenerate it in Hands-on 3B (not provided as a download)~/workshop

After downloading, your workshop directory should contain:

workshop/
├── arm_single.sv
├── arm_single_tb.sv
└── memfile.dat

4.2 Files students create or generate during the workshop

FilePurposeAction
add.cTiny C function for compiler observationCreate during Hands-on 1
add_armv7a.sCompiler-generated assemblyGenerated by GCC
simple_alu.v4-bit combinational ALUCreate during Hands-on 2
tb_simple_alu.vALU testbenchCreate during Hands-on 2
alu_simCompiled Icarus simulationGenerated by iverilog
alu.vcdALU waveformGenerated by vvp
simple_sum.sDeterministic A32 demo programCreate during Hands-on 3
simple_sum.oAssembled object fileGenerated by GCC
memfile.datOne machine-code word per line for instruction memoryGenerate from simple_sum.o
arm_single.outCompiled processor simulationGenerated by iverilog
arm_single.vcdFull processor waveformGenerated automatically by the revised testbench

Recovery aid: the reference copies are included so a student who loses time to an editing error can rejoin the workshop. The live exercise is more useful if you create the files yourself first.

↑ Back to top

5. Toolchain Overview

Windows-native workshop toolchain

The tools play different roles:

C source ──arm-none-eabi-gcc──> ARM assembly

ARM .s ──arm-none-eabi-gcc -c──> object file
object file ──arm-none-eabi-objdump──> assembly + machine words

Verilog/SystemVerilog sources ──iverilog──> compiled simulation
compiled simulation ──vvp──> execution + VCD file
VCD file ──gtkwave──> waveform inspection

Why this matters: iverilog and vvp are separate steps. Icarus documentation describes iverilog as the compiler/driver and vvp as the runtime that executes the compiled simulation.

↑ Back to top

6. What Is an ARM Instruction?

An instruction is a compact command specifying an operation and its operands. Consider:

ADD r2, r0, r1
  • ADD is the mnemonic: what operation to perform.
  • r2 is the destination: where the result is written.
  • r0 and r1 are the source operands.

In A32 state, the instruction is represented by a 32-bit machine word. Assembly is written for humans; the processor fetches the encoded word.

The workshop deliberately requests A32/Arm-state output with:

-marm

rather than Thumb output.

Check yourself: Which token selects the ALU operation in ADD r2, r0, r1?
Answer: the mnemonic/opcode, ADD.

↑ Back to top

7. ARM Registers and Load/Store Architecture

AArch32 exposes registers r0 through r15. For this workshop, the most useful names are:

  • r0–r3: commonly used for early function arguments, temporaries, and return values under common AArch32 calling conventions;
  • r13 / sp: stack pointer;
  • r14 / lr: link register, commonly holding a return address;
  • r15 / pc: program counter.

The architecture defines registers and instructions. A calling convention defines how software uses registers during function calls.

7.1 Load → operate → store

ARM is presented here as a load/store architecture: arithmetic operates on register values, while LDR and STR move data between registers and memory.

LDR r0, [r4, #8]    @ r0 <- memory[r4 + 8]
ADD r2, r0, r1      @ r2 <- r0 + r1
STR r2, [r4, #12]   @ memory[r4 + 12] <- r2

The simple mental model is:

Load → operate → store

Do not worry about the full set of ARM addressing modes today. For the workshop, base register plus immediate offset is sufficient.

↑ Back to top

8. Hands-on 1 — Generate ARM Assembly from C

Estimated time from the presentation: about 10 minutes.

Objective

Compile a small C function to ARMv7-A A32 assembly and identify the function label, registers, arithmetic operation, and return sequence.

Procedure

  1. In the MSYS2 UCRT64 terminal, make sure you are in your workshop folder:
cd ~/workshop
  1. Create the file:
notepad.exe add.c
  1. Enter exactly:
int add(int a, int b) {
    return a + b;
}
  1. Save and close Notepad.

  2. Generate unoptimized assembly:

arm-none-eabi-gcc -S \
  -march=armv7-a -marm -O0 \
  add.c -o add_armv7a.s
  1. Display the assembly:
cat add_armv7a.s
  1. Find four features:
    1. the function label add:;
    2. registers carrying or temporarily holding the two input values;
    3. an add instruction or equivalent arithmetic operation;
    4. the return sequence, commonly ending in bx lr.

Presentation consistency note: the teaching discussion in the deck explicitly explains the verbose -O0 case and then compares optimization. This handout therefore uses ASCII -O0 for the first run. Use -O1 in the comparison below.

What the options mean

OptionMeaning
-SStop after compilation and emit assembly source
-march=armv7-aTarget the ARMv7-A architecture
-marmGenerate A32/Arm-state instructions rather than Thumb
-O0Disable optimization for a more direct compiler output
-o add_armv7a.sName the output file

↑ Back to top

9. Read the Generated Assembly

Compiler output is not guaranteed to match the slide line for line. GCC version, optimization, and compiler decisions can change register allocation, stack-frame construction, directives, and comments.

A trimmed semantic pattern may look like:

add:
    ...                    @ optional prologue / argument handling
    add   r3, r2, r3       @ arithmetic
    mov   r0, r3           @ place return value in r0
    ...                    @ optional epilogue
    bx    lr               @ return

Focus on meaning:

  • Inputs: the first two integer arguments commonly arrive in r0 and r1.
  • Computation: at -O0, the compiler may copy, spill, and reload values before the actual ADD.
  • Return: the result commonly ends in r0; bx lr returns to the caller.

Compare with optimization

Run:

arm-none-eabi-gcc -S \
  -march=armv7-a -marm -O1 \
  add.c -o add_armv7a_O1.s

cat add_armv7a_O1.s

Ask yourself:

  • Which instructions disappeared?
  • Is the observable C behavior unchanged?
  • Which lines are the actual arithmetic and which support function entry/exit?

Optional machine-code view

arm-none-eabi-gcc -c \
  -march=armv7-a -marm -O0 \
  add.c -o add_armv7a.o

arm-none-eabi-objdump -d add_armv7a.o

This links the human-readable assembly to encoded instructions without requiring you to hand-encode every field.

↑ Back to top

10. Machine Code and memfile.dat

The simplified processor does not read .s assembly source. Its instruction memory uses $readmemh to read machine-code words from memfile.dat when the simulation starts.

Each line is one 32-bit instruction represented by 8 hexadecimal digits:

Assembly                         Machine code
ADD R3, R1, R2                   E0813002
STR R3, [R0, #248]               E58030F8
B halt                           EAFFFFFE

Assembly source to memfile.dat and instruction memory

The workflow used later is:

assembly source
      ↓
object file
      ↓
objdump
      ↓
machine-code words
      ↓
memfile.dat
      ↓
instruction memory

Why this matters: compiler-generated C assembly is excellent for learning. For the educational processor, however, a short handwritten .s file gives a deterministic instruction sequence that stays within the processor’s supported subset.

↑ Back to top

11. A Quick Look at A32 Instruction Fields

You do not need to memorize full ARM encoding. The presentation uses only enough decoding to connect a 32-bit word to the processor’s controller.

A32 fields used in the workshop

11.1 Condition field

Bits 31:28 are the condition field. In the workshop program, the instructions use condition code:

1110 = E = always execute

11.2 Main operation class

The simplified controller uses bits 27:26 to distinguish broad instruction classes:

00  data processing
01  memory access (LDR / STR)
10  branch

11.3 Example: ADD R3, R1, R2

E0813002

Important fields are:

  • condition = E / always;
  • operation class = data processing;
  • data-processing opcode = ADD;
  • Rn = R1;
  • Rd = R3;
  • register operand Rm = R2.

11.4 Memory and branch examples

LDR R1, [R0, #0]       @ E5901000
STR R3, [R0, #248]     @ E58030F8
B halt                  @ EAFFFFFE

The single-cycle processor does not implement an OUT or HLT instruction. The workshop models output as a store to a watched address and models halting as an infinite branch plus testbench-controlled $finish.

↑ Back to top

12. The Simplified ARM Processor

The supplied processor is an educational A32-compatible subset. Its decoder supports the instructions used in the presentation:

ADD
SUB
AND
ORR
LDR
STR
B

The source contains a controller, datapath, register file, ALU, immediate extender, multiplexers, PC logic, instruction memory, and data memory.

This is not a complete modern Arm core. Its purpose is to make instruction execution traceable.

The full demonstration program conceptually performs:

mem[0] = 3
mem[1] = 4

R1 = mem[0]
R2 = mem[1]
R3 = R1 + R2

store R3 to address 0xF8
branch forever

The data memory is not initialized from memfile.dat, so the program first writes 3 and 4 into mem[0] and mem[1] before loading them back.

↑ Back to top

13. The Single-Cycle Datapath

A microarchitecture is the hardware organization used to implement an architecture. In this teaching model, each instruction completes through the datapath in a single clock cycle.

Simplified single-cycle ARM datapath

For a register-to-register instruction such as:

ADD r2, r0, r1

trace the operation as follows:

  1. Fetch: the PC selects a word in instruction memory.
  2. Decode: instruction fields select r0, r1, and destination r2.
  3. Read operands: the register file presents the current source values.
  4. Execute: control selects the ADD function and the ALU computes the sum.
  5. Write back: the ALU result is written to r2.

No data-memory access is needed for this register-to-register ADD.

Check yourself: Which major datapath block is present but bypassed for ADD r2, r0, r1?
Answer: data memory.

↑ Back to top

14. Verilog/SystemVerilog Essentials

Verilog describes hardware, not a sequential list of software commands. A module represents logic that exists concurrently.

Three ideas are sufficient for this workshop:

  1. Module interface: inputs and outputs are signals crossing the hardware boundary.
  2. Combinational behavior: always @(*) describes logic whose output responds to current inputs.
  3. Testbench: a separate simulation-only module drives inputs, observes outputs, and can request waveform recording.

A combinational decision network can be written as:

always @(*) begin
    case (op)
        2'b00: y = a + b;
        // ...
    endcase
end

The full processor source uses SystemVerilog constructs such as logic, always_comb, and always_ff. Therefore the processor compile command uses:

-g2012

Icarus documents -g2012 as enabling the IEEE 1800-2012 SystemVerilog language level.

↑ Back to top

15. Hands-on 2A — Build the 4-bit ALU

Estimated time from the presentation: about 8 minutes.

Objective

Create a four-operation combinational ALU with two 4-bit inputs, a 2-bit operation selector, and a 4-bit result.

Operation table

opOperationExpression
00ADDa + b
01SUBa - b
10ANDa & b
11ORa | b

Because y is only four bits wide, arithmetic wraps modulo 16.

Procedure

  1. Create the source file:
notepad.exe simple_alu.v
  1. Enter:
module simple_alu(
    input  [3:0] a,
    input  [3:0] b,
    input  [1:0] op,
    output reg [3:0] y
);

always @(*) begin
    case(op)
        2'b00: y = a + b;
        2'b01: y = a - b;
        2'b10: y = a & b;
        2'b11: y = a | b;
    endcase
end

endmodule
  1. Save and close the file.

Before simulating, predict the outputs for:

a = 6
b = 3

↑ Back to top

16. Hands-on 2B — Test and Inspect the ALU

Create the testbench

  1. Create:
notepad.exe tb_simple_alu.v
  1. Enter:
module tb_simple_alu;

reg  [3:0] a, b;
reg  [1:0] op;
wire [3:0] y;

simple_alu uut (
    .a(a),
    .b(b),
    .op(op),
    .y(y)
);

initial begin
    $dumpfile("alu.vcd");
    $dumpvars(0, tb_simple_alu);

    a = 4'd6;
    b = 4'd3;

    op = 2'b00; #10;
    op = 2'b01; #10;
    op = 2'b10; #10;
    op = 2'b11; #10;

    $finish;
end

endmodule

Compile, simulate, inspect

  1. Compile:
iverilog -o alu_sim simple_alu.v tb_simple_alu.v
  1. Run:
vvp alu_sim
  1. Confirm the waveform exists:
ls -l alu.vcd
  1. Open GTKWave:
gtkwave alu.vcd
  1. In GTKWave, select hierarchy tb_simple_alu, then add:
a
b
op
y
  1. Zoom to the full 0–40 interval.

Expected behavior:

Expected ALU behavior for a=6 and b=3

TimeopOperationy decimaly binary
0–1000ADD91001
10–2001SUB30011
20–3010AND20010
30–4011OR70111

Engineering habit: predict → edit → compile → simulate → inspect → explain. A tool completing without error is not, by itself, proof that the design is correct.

↑ Back to top

17. Hands-on 3A — Write the Deterministic ARM Program

The C example was useful for observing the compiler, but the simplified processor supports only a small instruction subset. For a deterministic full-processor demonstration, write the exact assembly program used by the presentation.

Procedure

  1. In ~/workshop, create:
notepad.exe simple_sum.s
  1. Enter exactly:
.syntax unified
.arch armv7-a
.arm
.global _start

_start:
    SUB R0, R15, R15      @ R0 = 0
    ADD R1, R0, #3        @ R1 = 3
    STR R1, [R0, #0]      @ mem[0] = 3

    ADD R2, R0, #4        @ R2 = 4
    STR R2, [R0, #4]      @ mem[1] = 4

    LDR R1, [R0, #0]      @ R1 = mem[0]
    LDR R2, [R0, #4]      @ R2 = mem[1]
    ADD R3, R1, R2        @ R3 = R1 + R2 = 7

    STR R3, [R0, #248]    @ write result to testbench output address
halt:
    B halt                @ pseudo-HLT

What the program does

  • SUB R0, R15, R15 makes R0 = 0 and uses it as the base address.
  • The first two STR instructions initialize data-memory words 0 and 1.
  • The two LDR instructions read back 3 and 4.
  • ADD R3, R1, R2 produces 7.
  • The final STR writes 7 to byte address 248 = 0xF8.
  • B halt loops forever as a pseudo-halt.

Why initialize memory in software? The supplied dmem array is not loaded by $readmemh; only the instruction memory imem reads memfile.dat.

↑ Back to top

18. Hands-on 3B — Generate memfile.dat

Procedure

  1. Assemble the handwritten program:
arm-none-eabi-gcc -c \
  -march=armv7-a -marm \
  simple_sum.s -o simple_sum.o
  1. Inspect assembly and machine code side by side:
arm-none-eabi-objdump -d simple_sum.o
  1. Export one machine-code word per line:
arm-none-eabi-objdump -d simple_sum.o | \
  awk '/^[[:space:]]*[0-9a-f]+:/ {print toupper($2)}' > memfile.dat
  1. Inspect the result:
cat memfile.dat

Expected file:

E04F000F
E2801003
E5801000
E2802004
E5802004
E5901000
E5902004
E0813002
E58030F8
EAFFFFFE
  1. Verify that the processor files and machine-code file are together:
ls -l arm_single.sv arm_single_tb.sv memfile.dat

Expected result: the last two lines are E58030F8 and EAFFFFFE.

↑ Back to top

19. Separated Processor and Testbench Architecture

The revised workshop workflow keeps the processor design and simulation scaffolding in separate files.

Separated processor and testbench architecture

arm_single.sv
    processor + memories
    ├── top
    ├── arm
    │   ├── controller
    │   └── datapath
    ├── imem
    └── dmem

arm_single_tb.sv
    clock generation
    reset generation
    +CYCLES=N run-time control
    success self-check
    waveform dumping

memfile.dat
    ARM machine-code program

The exact supplied processor uses:

initial
    $readmemh("memfile.dat", RAM);

inside imem. The revised testbench already contains:

$dumpfile("arm_single.vcd");
$dumpvars(0, testbench);

Therefore do not manually add $dumpfile or $dumpvars to the processor source.

↑ Back to top

20. Hands-on 3C — Compile and Run the Processor

Procedure

  1. Confirm the folder contains:
arm_single.sv
arm_single_tb.sv
memfile.dat
  1. Compile both SystemVerilog files together:
iverilog -g2012 -Wall -s testbench \
  -o arm_single.out arm_single.sv arm_single_tb.sv
  1. Run for 25 active processor cycles:
vvp arm_single.out +CYCLES=25
  1. Observe the terminal messages. A successful run should report that the success condition was seen and then complete at the selected cycle limit.

  2. Confirm the waveform was generated:

ls -l arm_single.vcd
  1. Open it:
gtkwave arm_single.vcd

What the compile options mean

  • -g2012 — enable SystemVerilog 2012 syntax used by the source.
  • -Wall — enable a broad set of Icarus warnings.
  • -s testbench — select testbench as the top-level root module.
  • -o arm_single.out — name the compiled simulation.
  • both .sv files are listed because the design and testbench are separate.

Expected result: the testbench watches for a store of decimal 7 to byte address 0xF8. It records success but continues until the requested number of post-reset cycles has completed.

↑ Back to top

21. Runtime Cycle Control and Recompilation Rules

The revised testbench reads a plusarg at run time:

vvp arm_single.out +CYCLES=10
vvp arm_single.out +CYCLES=25
vvp arm_single.out +CYCLES=50
vvp arm_single.out +CYCLES=100

If the argument is omitted:

vvp arm_single.out

the testbench uses:

DEFAULT_CYCLES = 25

21.1 What CYCLES counts

CYCLES counts rising-edge processor cycles after reset is released.

The supplied testbench uses:

clock period = 10 ns
reset high for first 22 ns

The first active rising edge is therefore after the reset pulse has ended.

21.2 When to recompile

Recompile with iverilog when:

  • arm_single.sv changes;
  • arm_single_tb.sv changes.

Run again:

iverilog -g2012 -Wall -s testbench \
  -o arm_single.out arm_single.sv arm_single_tb.sv

Do not recompile when only +CYCLES=N changes. Simply rerun:

vvp arm_single.out +CYCLES=50

21.3 What if only memfile.dat changes?

You also do not need to recompile. $readmemh("memfile.dat", RAM) is executed when the simulation starts, so the next vvp run reads the current contents of memfile.dat.

Use:

vvp arm_single.out +CYCLES=25

again after regenerating memfile.dat.

↑ Back to top

22. GTKWave Inspection of the ARM Execution

Open:

gtkwave arm_single.vcd

22.1 Navigate the hierarchy

  1. In the hierarchy pane, expand testbench.
  2. Select dut for top-level processor-system signals.
  3. Expand dut.arm and dut.arm.dp if you want deeper datapath signals.
  4. Append the required traces to the waveform panel.

Start with:

clk
reset
dut.PC
dut.Instr
dut.DataAdr
dut.WriteData
dut.MemWrite
dut.ReadData

The presentation also recommends useful internal signals where available:

dut.arm.ALUResult
dut.arm.dp.ReadData
dut.arm.dp.SrcA
dut.arm.dp.SrcB
dut.arm.dp.ALUResult

22.3 Change buses to hexadecimal

For 32-bit buses, hexadecimal is easier to compare with assembly and memfile.dat.

In GTKWave:

  1. select a bus trace;
  2. right-click;
  3. choose Data Format → Hex.

GTKWave documentation supports displaying multi-bit vectors in bases including hexadecimal.

22.4 Zoom and follow the instruction stream

Use Zoom Full first. Then zoom around the final MemWrite pulse.

Look for:

  • reset starting high and then falling low;
  • PC progressing through byte addresses 0x00, 0x04, 0x08, …;
  • Instr matching the successive 32-bit words in memfile.dat;
  • store pulses on MemWrite;
  • ReadData showing 3 and 4 during the two LDR instructions;
  • the ALU result becoming 7 after ADD R3, R1, R2;
  • the final successful store.

↑ Back to top

23. Expected Processor Behaviour and Verification Checklist

The demonstration program is successful when this transaction appears:

MemWrite   = 1
DataAdr    = 0x000000F8
WriteData  = 0x00000007

Why 0xF8?

word index = 62
byte address = 62 × 4 bytes
             = 248 decimal
             = 0xF8

The data memory uses:

RAM[a[31:2]]

so the external byte address 0xF8 selects internal word 62.

Verification checklist

  • reset begins high and then goes low.
  • PC starts at 0 after reset and normally advances by four bytes per instruction.
  • Instr follows the words in memfile.dat.
  • MemWrite pulses for STR R1,[R0,#0].
  • MemWrite pulses for STR R2,[R0,#4].
  • ReadData shows 0x00000003 during the first load.
  • ReadData shows 0x00000004 during the second load.
  • the ADD result reaches 0x00000007.
  • the final store has DataAdr = 0x000000F8.
  • the final store has WriteData = 0x00000007.
  • the testbench prints that the success condition was observed.
  • the B halt instruction causes repeated execution of the halt loop until the cycle limit ends the simulation.

Minimum useful GTKWave trace: clk, reset, PC, Instr, DataAdr/ALUResult, WriteData, MemWrite, and ReadData, with 32-bit buses displayed in hexadecimal.

↑ Back to top

24. Troubleshooting

arm-none-eabi-gcc: command not found

Check that you opened MSYS2 UCRT64, then install the Arm GCC package:

pacman -S mingw-w64-ucrt-x86_64-arm-none-eabi-gcc

Close and reopen UCRT64 if the path still has not refreshed.

iverilog: command not found or vvp: command not found

Install the Icarus UCRT64 package:

pacman -S mingw-w64-ucrt-x86_64-iverilog

The package includes both executables.

Unable to open memfile.dat / $readmemh error

The instruction memory opens memfile.dat using a relative path. Run vvp from the folder containing the file:

pwd
ls -l arm_single.sv arm_single_tb.sv memfile.dat

Then run:

vvp arm_single.out +CYCLES=25

File appears missing even though you saved it in Notepad

Windows may have saved a hidden .txt extension. Check:

ls -l

Look for names such as add.c.txt or simple_alu.v.txt.

Verilog syntax error in the ALU exercise

Check:

  • semicolons;
  • matching begin / end;
  • case / endcase;
  • module / endmodule;
  • straight ASCII quotation marks;
  • exact literal syntax such as 2'b00.

arm_single.vcd is not created

The revised testbench already contains the VCD dump statements. Check that:

  1. arm_single_tb.sv was included in the compile command;
  2. -s testbench selected the correct top module;
  3. compilation succeeded;
  4. you actually ran vvp arm_single.out ....

Do not add another $dumpfile or $dumpvars to arm_single.sv.

GTKWave opens but no traces are visible

Opening a VCD does not automatically append every signal to the wave panel. Select the appropriate hierarchy, select the signals, and use Append or Insert.

Bus values are hard to read

Change 32-bit buses to hexadecimal:

Right-click → Data Format → Hex

Unknown x values appear

Uninitialized registers or memory words can legitimately appear as x until they are written. In this processor, the data RAM is not pre-initialized. The demo program explicitly writes 3 and 4 before loading those locations.

If x values remain on values that should already have been initialized, verify the instruction sequence and the earlier write pulses.

The final value is wrong

Check in this order:

  1. cat memfile.dat and compare it with the expected ten words;
  2. verify simple_sum.s exactly matches the workshop program;
  3. confirm the simulation ran long enough;
  4. inspect PC and Instr to ensure the program is being fetched correctly;
  5. inspect the two initialization stores;
  6. inspect the two loads (ReadData = 3, then 4);
  7. inspect the ADD result;
  8. inspect the final MemWrite, DataAdr, and WriteData together.

The C-generated assembly differs from the handout

That is expected. Compiler version and optimization choices affect prologues, temporary registers, and exact instruction sequences. Identify the function label, addition, result placement, and return semantics rather than comparing every line.

Fast recovery sequence

pwd
ls -l
arm-none-eabi-gcc --version
arm-none-eabi-objdump --version
iverilog -V
vvp -V

↑ Back to top

25. Suggested Exercises and Extensions

These are deliberately small extensions. Keep the same workflow: predict → edit → compile → simulate → inspect → explain.

Exercise A — change the ALU inputs

Change:

a = 4'd9;
b = 4'd2;

Predict before simulating.

Expected values:

ADD = 11
SUB = 7
AND = 0
OR  = 11

Explain 9 AND 2 bit by bit using:

1001
0010
----
0000

Exercise B — demonstrate XOR

A 2-bit selector already has only four encodings. For a minimal demonstration, temporarily replace AND:

2'b10: y = a ^ b;

A more complete extension would widen op and update the testbench.

Exercise C — add a zero flag

Add an output and drive it high whenever y is zero:

output zero;
assign zero = (y == 4'b0000);

Then update the testbench and inspect zero in GTKWave.

Exercise D — change only the processor run length

Without changing source files, compare:

vvp arm_single.out +CYCLES=10
vvp arm_single.out +CYCLES=25
vvp arm_single.out +CYCLES=50

Which run first contains the successful store? Why is recompilation unnecessary?

Exercise E — relate machine code to waveform

Pick one instruction from memfile.dat, find the same value on dut.Instr, and explain what changes in the datapath during that cycle.

Beyond this workshop

A natural curriculum path is:

ALU
  ↓
register file
  ↓
control decoder
  ↓
memory interface
  ↓
single-cycle processor
  ↓
instruction extension / FPGA project

The supplied educational processor is intentionally a stepping stone rather than an endpoint.

↑ Back to top

26. Summary and References

Three takeaways

  1. Code becomes instructions. A compiler translates C into instructions encoded for a target architecture.
  2. Datapaths execute instructions. Registers, ALU, memory interfaces, multiplexers, and control logic cooperate to change architectural state.
  3. Verilog/SystemVerilog makes hardware observable. A design plus a testbench can be compiled, simulated, and inspected before hardware implementation.

Workshop and textbook reference

  • Sarah L. Harris and David Money Harris, Digital Design and Computer Architecture: ARM Edition, 1st ed., Morgan Kaufmann/Elsevier, 2015. The presentation uses Chapters 6–7 as its conceptual foundation.

Official technical references

↑ Back to top