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.
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!