Assignment Chef icon Assignment Chef
All English tutorials

Programming lesson

Building a Tiny Machine Simulator in C: A Step-by-Step Tutorial for CDA3103

Learn how to write a C program that simulates a Tiny Machine Architecture with separate instruction and data memory. This tutorial covers the ISA, fetch-execute cycle, structs, and debugging tips for your CDA3103 homework.

Tiny Machine Architecture CDA3103 homework C programming simulator instruction set architecture fetch execute cycle computer organization tutorial Tiny Machine ISA CPU simulation in C assembly language simulator computer architecture assignment low level programming C program counter accumulator data memory instruction memory C struct for instructions student programming help 2026 computer science

Introduction to the Tiny Machine Simulator

If you're taking CDA3103 this semester (Spring 2026), you've probably encountered the classic Tiny Machine Architecture assignment. This project challenges you to simulate a simple CPU with separate instruction memory (IM) and data memory (DM), implementing a basic instruction set architecture (ISA). In this tutorial, we'll break down the key concepts, provide a clear structure for your C code, and share tips to avoid common pitfalls. Whether you're a computer science student or a hobbyist exploring low-level computing, understanding how a CPU fetches, decodes, and executes instructions is fundamental.

Understanding the Tiny Machine Architecture

The Tiny Machine is a simplified model of a CPU. Its components include:

  • Program Counter (PC): Points to the next instruction in IM.
  • Instruction Register (IR): Holds the current instruction being executed.
  • Memory Address Registers (MAR, MAR2): Hold addresses for IM and DM access.
  • Memory Data Registers (MDR, MDR2): Hold data read from or written to memory.
  • Accumulator (A): Stores intermediate results.
  • Instruction Memory (IM): An array of instructions (opcode + address/device).
  • Data Memory (DM): An integer array for data storage.

The ISA consists of nine instructions: LOAD (1), ADD (2), STORE (3), SUB (4), IN (5), OUT (6), END (7), JMP (8), SKIPZ (9). Each instruction is executed in a fetch-execute cycle.

Setting Up the C Program Structure

Start by defining the necessary data structures. As the assignment hints, a struct for instructions simplifies IM management.

#include <stdio.h>
#include <stdlib.h>
#define MAX_PROG_SIZE 100
#define MAX_DATA_SIZE 10

typedef struct {
    int opCode;
    int deviceOrAddress;
} Instruction;

Instruction IM[MAX_PROG_SIZE];
int DM[MAX_DATA_SIZE];
int PC = 0;
Instruction IR;
Instruction MDR1;
int MAR, MAR2;
int MDR2;
int A = 0;
int run = 1;

This setup mirrors the hardware registers. The run flag controls the instruction cycle.

Reading the Input File

Your simulator should read a text file containing pairs of integers (opcode and operand). Use command-line arguments to pass the filename.

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <inputfile>\n", argv[0]);
        return 1;
    }
    FILE *fp = fopen(argv[1], "r");
    if (!fp) {
        printf("Error opening file.\n");
        return 1;
    }
    int op, addr;
    int i = 0;
    while (fscanf(fp, "%d %d", &op, &addr) == 2) {
        IM[i].opCode = op;
        IM[i].deviceOrAddress = addr;
        i++;
    }
    fclose(fp);
    // ... fetch-execute loop
}

This reads instructions into IM. Ensure you handle file errors gracefully.

Implementing the Fetch-Execute Cycle

The core of your simulator is a while loop that fetches and executes instructions until END or run becomes 0.

while (run) {
    // FETCH phase
    MAR = PC;
    PC = PC + 1;
    MDR1 = IM[MAR];
    IR = MDR1;
    
    // EXECUTE phase based on IR.opCode
    switch (IR.opCode) {
        case 1: // LOAD
            MAR2 = IR.deviceOrAddress;
            MDR2 = DM[MAR2];
            A = MDR2;
            break;
        case 2: // ADD
            MAR2 = IR.deviceOrAddress;
            MDR2 = DM[MAR2];
            A = A + MDR2;
            break;
        case 3: // STORE
            MAR2 = IR.deviceOrAddress;
            MDR2 = A;
            DM[MAR2] = MDR2;
            break;
        case 4: // SUB
            MAR2 = IR.deviceOrAddress;
            MDR2 = DM[MAR2];
            A = A - MDR2;
            break;
        case 5: // IN
            printf("Enter input value: ");
            scanf("%d", &A);
            break;
        case 6: // OUT
            printf("Output: %d\n", A);
            break;
        case 7: // END
            run = 0;
            break;
        case 8: // JMP
            PC = IR.deviceOrAddress;
            break;
        case 9: // SKIPZ
            if (A == 0) {
                PC = PC + 1;
            }
            break;
        default:
            printf("Unknown opcode: %d\n", IR.opCode);
            run = 0;
    }
    // Print status after each instruction
    printStatus();
}
printf("Program complete.\n");

Notice that after each instruction, we call printStatus() to display the state. This is required for debugging and grading.

Printing Status Messages

Your output should show PC, Accumulator, and Data Memory after each instruction. Use a helper function:

void printStatus() {
    printf("PC = %d | A = %d | DM = [", PC, A);
    for (int i = 0; i < MAX_DATA_SIZE; i++) {
        printf("%d", DM[i]);
        if (i < MAX_DATA_SIZE - 1) printf(", ");
    }
    printf("]\n");
}

For the IN instruction, you might want to print a prompt before reading input. The example in the assignment shows a comment like /* input value */ – you can replicate that with a simple printf.

Testing with Sample Input

Consider the example input file provided:

5 5
6 7
3 0
5 5
6 7
3 1
1 0
4 1
3 0
6 7
1 1
6 7
7 0

This program reads two numbers, stores them, computes their difference, and outputs the result. Run your simulator with this file and verify the output matches expectations.

Common Pitfalls and Debugging Tips

Students often make these mistakes:

  • Off-by-one errors: Remember that PC starts at 0 and increments after fetch. Your IM array indices should match the instruction order.
  • Forgetting to initialize DM: DM should be all zeros initially. Use int DM[MAX_DATA_SIZE] = {0};.
  • Misunderstanding SKIPZ: SKIPZ increments PC by 1 only if A == 0. It does not skip the next instruction unconditionally.
  • Not handling END properly: Set run = 0 and break out of the loop.
  • Input file format: Ensure your file has exactly two integers per line, separated by whitespace.

Relating to Real-World Computing

While the Tiny Machine is a teaching tool, its concepts are used everywhere. For example, the fetch-execute cycle is the heart of every modern CPU, from your laptop to the servers running AI models like ChatGPT. Understanding how instructions are stored and processed helps you appreciate optimization techniques like pipelining and caching. In 2026, with the rise of edge AI and custom chips, low-level programming skills are more valuable than ever.

Conclusion

Building a Tiny Machine simulator in C is a rewarding project that solidifies your understanding of computer architecture. By following the steps above—defining structs, reading input, implementing the fetch-execute cycle, and printing status—you'll have a working simulator ready for submission. Remember to test thoroughly and comment your code. Good luck with your CDA3103 homework!