Computer architecture is the design and organization of a computer system, including its instruction set, processor, memory, input/output devices, and interconnection. Computer architecture determines how a computer executes programs, as well as how it communicates with other computers and devices. Computer architecture also influences the cost, power consumption, reliability, and security of a computer system.
In this article, we will explore the concepts of assembly language and computer architecture using C and Java TM, two popular high-level programming languages. We will use a simple, horizontally microprogrammed computer called H1 as a unifying theme. H1 is a hypothetical computer that was designed by Anthony J. Dos Reis for his book "Assembly Language and Computer Architecture Using C++ and Java" . H1 has a simple instruction set that can be easily implemented in both C and Java TM. H1 also has a microlevel that defines how each instruction is executed by the hardware.
H1 Assembly Language
H1 assembly language is a symbolic representation of the binary machine code that H1 can execute. Each assembly instruction consists of an operation code (opcode) and zero or more operands. The opcode specifies what kind of operation to perform, such as arithmetic, logic, load, store, branch, or jump. The operands specify the data or addresses involved in the operation, such as registers, memory locations, or immediate values.
H1 has 16 general-purpose registers (R0-R15) that can store 32-bit values. R0 is always zero, R14 is used as the stack pointer (SP), and R15 is used as the program counter (PC). H1 also has a status register (SR) that contains four flags: negative (N), zero (Z), overflow (V), and carry (C). These flags are set or cleared by certain instructions to indicate the result of an operation.
H1 has two addressing modes: immediate and direct. Immediate addressing means that the operand is a constant value that is part of the instruction. Direct addressing means that the operand is a memory address that is part of the instruction. H1 uses little-endian byte ordering, which means that the least significant byte of a word is stored at the lowest address.
H1 assembly language has a simple syntax that follows these rules:
Each instruction occupies one line.
Comments start with a semicolon (;) and extend to the end of the line.
Labels are optional identifiers that mark the location of an instruction or data. Labels must start with a letter and end with a colon (:).
Opcodes are mnemonics that represent the operation codes. Opcodes must be in uppercase.
Operands are separated by commas (,) and enclosed in brackets ([ ]) for direct addressing.
Immediate values are preceded by a hash sign (#).
Hexadecimal values are preceded by a dollar sign ($).
Here is an example of H1 assembly language that calculates the factorial of 5:
```assembly ; Factorial of 5 MOV R1,#5 ; R1 = 5 MOV R2,#1 ; R2 = 1 LOOP: MUL R2,R1 ; R2 = R2 * R1
SUB R1,#1 ; R1 = R1 - 1 BNE LOOP ; Branch if R1 != 0 HALT ; Stop execution ``` H1 Microlevel
The microlevel of H1 defines how each instruction is executed by the hardware. The microlevel consists of microinstructions that control the signals and data paths of the processor. Each microinstruction consists of four fields: next address (NA), condition code (CC), control signals (CS), and data signals (DS). The NA field specifies the address of the next microinstruction to fetch from the control store. The CC field specifies the condition under which the NA field is used; otherwise, the default next address is incremented by one. The CS field specifies which control signals to activate or deactivate, such as enabling or disabling registers, memory, or arithmetic logic unit (ALU). The DS field specifies which data signals to select for the ALU inputs and output.
The microlevel of H1 is implemented using a horizontal microcode, which means that each microinstruction is a fixed-length bit pattern that directly corresponds to the signals and data paths of the processor. The microlevel of H1 has 36 bits, divided into four fields as follows:
NA CC CS DS
----------------
9 bits 4 bits 12 bits 11 bits
The NA field can specify any of the 512 addresses in the control store. The CC field can specify any of the 16 possible conditions, such as N, Z, V, C, or unconditional. The CS field can specify any combination of the 12 control signals, such as ENA, ENB, ENC, ENM, WRM, WRR, etc. The DS field can specify any of the 11 data signals, such as A, B, C, M, R0-R3, etc.
Here is an example of H1 microcode that implements the MUL instruction:
```microcode ; MUL Rd,Rs ; Rd = Rd * Rs ; N Z V C flags are affected 000001100 0000 000010010000 0010 ; Fetch instruction and increment PC
000001101 0000 000001000001 0100 ; Decode instruction and select Rd
000001110 0000 000000100010 0101 ; Select Rs and multiply
000001111 0000 000100000100 0110 ; Write result to Rd and set flags
000010000 1111 000000000000 0000 ; Return to fetch
``` H1 Implementation in C and Java TM
H1 can be implemented in both C and Java TM using arrays, structures, classes, and methods. The main components of H1 are the memory, the registers, the status register, the control store, and the processor. The memory is an array of bytes that can store instructions and data. The registers are an array of words that can store values. The status register is a structure or a class that can store the flags. The control store is an array of words that can store the microinstructions. The processor is a function or a method that can execute the microinstructions.
Here is an example of H1 implementation in C:
```c #include
#include
#define MEM_SIZE 65536 // Memory size in bytes #define REG_SIZE 16 // Number of registers #define CS_SIZE 512 // Control store size in words // Memory array unsigned char memory[MEM_SIZE];
// Register array unsigned int registers[REG_SIZE];
// Status register structure struct status_register
unsigned int N : 1; // Negative flag unsigned int Z : 1; // Zero flag unsigned int V : 1; // Overflow flag unsigned int C : 1; // Carry flag ; // Status register variable struct status_register SR;
// Control store array unsigned int control_store[CS_SIZE];
// Processor function void processor()
// Microinstruction fields unsigned int NA; // Next address unsigned int CC; // Condition code unsigned int CS; // Control signals unsigned int DS; // Data signals // Control signals unsigned int ENA; // Enable register A unsigned int ENB; // Enable register B unsigned int ENC; // Enable register C unsigned int ENM; // Enable memory unsigned int WRM; // Write to memory unsigned int WRR; // Write to register unsigned int ALU; // ALU operation code // Data signals unsigned int A; // ALU input A unsigned int B; // ALU input B unsigned int C; // ALU output C // Other variables unsigned int MAR; // Memory address register unsigned int MDR; // Memory data register unsigned int IR; // Instruction register // Initialize PC and CSAR to zero registers[15] = 0; unsigned int CSAR = 0; while (1) // Fetch microinstruction from control store using CSAR unsigned int MI = control_store[CSAR]; // Decode microinstruction fields using bit masks and shifts NA = (MI & 0xFF800000) >> 23; CC = (MI & 0x00780000) >> 19; CS = (MI & 0x0007F800) >> 11; ```c 000007FF); // Data signals
// Decode control signals using bit masks and shifts ENA = (CS & 0x00000800) >> 11; ENB = (CS & 0x00000400) >> 10; ENC = (CS & 0x00000200) >> 9; ENM = (CS & 0x00000100) >> 8; WRM = (CS & 0x00000080) >> 7; WRR = (CS & 0x00000040) >> 6; ALU = (CS & 0x0000003F); // Decode data signals using bit masks and shifts A = (DS & 0x000007C0) >> 6; B = (DS & 0x0000003E) >> 1; C = (DS & 0x00000001); // Execute microinstruction based on control and data signals if (ENA) // Enable register A and select its value switch (A) case 0: A = registers[0]; break; // R0 case 1: A = registers[1]; break; // R1 case 2: A = registers[2]; break; // R2 case 3: A = registers[3]; break; // R3 case 4: A = SR.N; break; // N flag case 5: A = SR.Z; break; // Z flag case 6: A = SR.V; break; // V flag case 7: A = SR.C; break; // C flag default: A = 0; break; // Invalid if (ENB) // Enable register B and select its value switch (B) case 0: B = registers[0]; break; // R0 case 1: B = registers[1]; break; // R1 case 2: B = registers[2]; break; // R2 case 3: B = registers[3]; break; // R3 case 4: B = SR.N; break; // N flag case 5: B = SR.Z; break; // Z flag case 6: B = SR.V; break; // V flag case 7: B = SR.C; break; // C flag default: B = 0; break; // Invalid if (ENC) // Enable register C and select its value switch (C) case 0: C = registers[0]; break; // R0 case 1: C = registers[1]; break; // R1 case 2: C = registers[2]; break; // R2 case 3: C = registers[3]; break; // R3 default: C = 0; break; // Invalid if (ENM) ((unsigned int)memory[MAR + 2] 0xFF000000) >> 24;
memory[MAR + 1] = (MDR & 0x00FF0000) >> 16; memory[MAR + 2] = (MDR & 0x0000FF00) >> 8; memory[MAR + 3] = (MDR & 0x000000FF); if (WRR) // Write to register using C and MDR registers[C] = MDR; // Perform ALU operation using A and B and store the result in MDR switch (ALU) case 0: MDR = A + B; break; // ADD case 1: MDR = A - B; break; // SUB case 2: MDR = A * B; break; // MUL case 3: MDR = A / B; break; // DIV case 4: MDR = A & B; break; // AND case 5: MDR = A // Set or clear flags based on the result of the ALU operation SR.N = (MDR & 0x80000000) ? 1 : 0; // Set N if the most significant bit is 1 SR.Z = (MDR == 0) ? 1 : 0; // Set Z if the result is zero SR.V = ((A & 0x80000000) == (B & 0x80000000)) && ((MDR & 0x80000000) != (A & 0x80000000)) ? 1 : 0; // Set V if there is a signed overflow SR.C = ((unsigned long long)A + (unsigned long long)B > 0xFFFFFFFF) ? 1 : 0; // Set C if there is an unsigned overflow // Update CSAR based on the condition code and the next address switch (CC) case 0: CSAR = NA; break; // Unconditional case 1: CSAR = SR.N ? NA : CSAR + 1; break; // N flag is set case 2: CSAR = SR.Z ? NA : CSAR + 1; break; // Z flag is set case 3: CSAR = SR.V ? NA : CSAR + 1; break; // V flag is set case 4: CSAR = SR.C ? NA : CSAR + 1; break; // C flag is set case 5: CSAR = !SR.N ? NA : CSAR + 1; break;// N flag is clear case 6: CSAR = !SR.Z ? NA : CSAR + 1; break;// Z flag is clear case 7: CSAR = !SR.V ? NA : CSAR + 1; break;// V flag is clear case 8: CSAR = !SR.C ? NA : CSAR + 1; break;// C flag is clear default: CSAR++; break; // Invalid condition code, increment by one // Check for HALT instruction and exit the loop if found if (IR == 0xFFFFFFFF) printf("HALT instruction executed.\n"); break; // Main function int main()
// Load memory, registers, status register, and control store from files or hard-coded values // Call processor function to start execution // Print memory, registers, status register, and control store to files or console return EXIT_SUCCESS; ``` Here is an example of H1 implementation in Java TM:
```java import java.io.*;
import java.util.*;
public class H1
private static final int MEM_SIZE = 65536; // Memory size in bytes private static final int REG_SIZE = 16; // Number of registers private static final int CS_SIZE = 512; // Control store size in words // Memory array private static byte[] memory; // Register array private static int[] registers; // Status register class private static class StatusRegister public boolean N; // Negative flag public boolean Z; // Zero flag public boolean V; // Overflow flag public boolean C; // Carry flag // Status register object private static StatusRegister SR; // Control store array private static int[] control_store; // Processor method public static void processor() // Microinstruction fields int NA; // Next address int CC; // Condition code int CS; // Control signals int DS; // Data signals // Control signals boolean ENA; // Enable register A boolean ENB; // Enable register B boolean ENC; // Enable register C boolean ENM; // Enable memory boolean WRM; // Write to memory boolean WRR; // Write to register int ALU; // ALU operation code // Data signals int A; // ALU input A int B; // ALU input B int C; // ALU output C // Other variables int MAR; // Memory address register int MDR; // Memory data register int IR; // Instruction register // Initialize PC and CSAR to zero registers[15] = 0; int CSAR = 0; while (true) // Fetch microinstruction from control store using CSAR int MI = control_store[CSAR]; // Decode microinstruction fields using bit masks and shifts NA = (MI & 0xFF800000) >>> 23; CC = (MI & 0x00780000) >>> 19; CS = (MI & 0x0007F800) >>> 11; DS = (MI & 0x000007FF); // Decode control signals using bit masks and shifts ENA = ((CS & 0x00000800) >>> 11) == 1; ENB = ((CS & 0x00000400) >>> 10) == 1; ENC = ((CS & 0x00000200) >>> 9) == 1; ENM = ((CS & 0x00000100) >>> 8) == 1; WRM = ((CS & 0x00000080) >>> 7) == 1; WRR = ((CS & 0x00000040) >>> 6) == 1; ALU = (CS & 0x0000003F); // Decode data signals using bit masks and shifts A = (DS & 0x000007C0) >>> 6; B = (DS & 0x0000003E) >>> 1; C = (DS & 0x00000001); // Execute microinstruction based on control and data signals if (ENA) // Enable register A and select its value switch (A) case 0: A = registers[0]; break; // R0 case 1: A = registers[1]; break; // R1 case 2: A = registers[2]; break; // R2 case 3: A = registers[3]; break; // R3 case 4: A = SR.N ? 1 : 0; break; // N flag case 5: A = SR.Z ? 1 : 0; break; // Z flag case 6: A = SR.V ? 1 : 0; break; // V flag ```java 1 : 0; break; // C flag
default: A = 0; break; // Invalid if (ENB) // Enable register B and select its value switch (B) case 0: B = registers[0]; break; // R0 case 1: B = registers[1]; break; // R1 case 2: B = registers[2]; break; // R2 case 3: B = registers[3]; break; // R3 case 4: B = SR.N ? 1 : 0; break; // N flag case 5: B = SR.Z ? 1 : 0; break; // Z flag case 6: B = SR.V ? 1 : 0; break; // V flag case 7: B = SR.C ? 1 : 0; break; // C flag
default: B = 0; break; // Invalid if (ENC) // Enable register C and select its value switch (C) case 0: C = registers[0]; break; // R0 case 1: C = registers[1]; break; // R1 case 2: C = registers[2]; break; // R2 case 3: C = registers[3]; break; // R3 default: C = 0; break; // Invalid if (ENM) ((int)memory[MAR + 1] >> 24); memory[MAR + 1] = (byte)((MDR & 0x00FF0000) >>> 16); memory[MAR + 2] = (byte)((MDR & 0x0000FF00) >>> 8); ```java 0x000000FF);
if (WRR) // Write to register using C and MDR registers[C] = MDR; // Perform ALU operation using A and B and store the result in MDR switch (ALU) B; break; // OR case 6: MDR = A ^ B; break; // XOR case 7: MDR = A; break; // NOT case 8: MDR = A
>> B; break;// SHR default: MDR = 0; break; // Invalid // Set or clear flags based on the result of the ALU operation SR.N = (MDR & 0x80000000) != 0; // Set N if the most significant bit is 1 SR.Z = (MDR == 0); // Set Z if the result is zero SR.V = ((A & 0x80000000) == (B & 0x80000000)) && ((MDR & 0x80000000) != (A & 0x80000000)); // Set V if there is a signed overflow SR.C = ((long)A + (long)B > 0xFFFFFFFFL); // Set C if there is an unsigned overflow // Update CSAR based on the condition code and the next address switch (CC) case 0: CSAR = NA; break; // Unconditional case 1: CSAR = SR.N ? NA : CSAR + 1; break; // N flag is set case 2: CSAR = SR.Z ? NA : CSAR + 1; break; // Z flag is set case 3: CSAR = SR.V ? NA : CSAR + 1; break; // V flag is set case 4: CSAR = SR.C ? NA : CSAR + 1; break; // C flag is set case 5: CSAR = !SR.N ? NA : CSAR + 1; break;// N flag is clear case 6: CSAR = !SR.Z ? NA : CSAR + 1; break;// Z flag is clear case 7: CSAR = !SR.V ? NA : CSAR + 1; break;// V flag is clear case 8: CSAR = !SR.C ? NA : CSAR + 1; break;// C flag is clear default: CSAR++; break; // Invalid condition code, increment by one // Check for HALT instruction and exit the loop if found if (IR == -1) System.out.println("HALT instruction executed."); break;