Microprogrammed Control and Programming the Basic Computer
Microprogrammed Control
In computer architecture, the control unit is responsible for directing the operation of the processor. It fetches instructions from memory, decodes them, and then generates the control signals that execute the instruction. There are two primary approaches to designing a control unit: hardwired control and microprogrammed control. This section focuses on microprogrammed control.
In a microprogrammed control unit, the control signals are generated by a special program stored in a dedicated memory called the control memory. Each instruction in the computer's instruction set is represented by a sequence of microinstructions. A microinstruction is a low-level instruction that specifies the operations to be performed by the processor's components, such as the ALU, registers, and data paths. The sequence of microinstructions for a given instruction is called a microprogram.
How Microprogrammed Control Works
The control memory stores the microprograms. When the processor fetches an instruction from the main memory, the control unit uses the instruction's opcode to determine which microprogram needs to be executed. It then fetches the first microinstruction from the control memory and executes it. Each microinstruction typically contains:
- Control Bits: These bits directly control the various components of the processor (e.g., ALU operation, register transfers, memory read/write).
- Next Address Generator: This part of the microinstruction specifies the address of the next microinstruction to be fetched. This can be a direct jump, a conditional jump based on processor flags, or a sequence to the next microinstruction in the control memory.
The control unit reads microinstructions sequentially or based on the next address information until the entire microprogram for the current machine instruction is completed. Upon completion, the control unit fetches the next machine instruction from main memory.
Advantages of Microprogrammed Control
- Flexibility: The control unit's logic can be easily modified by changing the microprogram in the control memory. This allows for easier implementation of new instructions or modifications to existing ones without altering the hardware.
- Simpler Design: For complex instruction sets, microprogramming can simplify the hardware design compared to hardwired control, where every control signal path needs to be explicitly wired.
- Easier Debugging: Microprograms can be debugged like software, making it easier to find and fix errors in the control logic.
Disadvantages of Microprogrammed Control
- Speed: Microprogrammed control is generally slower than hardwired control because fetching and executing microinstructions takes time. Each machine instruction requires multiple microinstruction fetches and executions.
- Control Memory Overhead: The control memory requires additional hardware, which can increase the cost and complexity of the system.
Types of Microprogrammed Control
There are two main types of microprogrammed control:
- Control Unit with ROM: The microprogram is stored in a Read-Only Memory (ROM). This is suitable for fixed microprograms that do not need to be changed after manufacturing.
- Control Unit with RAM: The microprogram is stored in a Random Access Memory (RAM). This allows the microprogram to be loaded from main memory or an external device, making it more flexible and updatable. This is known as a dynamic microprogram.
Control Word
A control word is a set of bits that are part of a microinstruction. Each bit or group of bits in the control word corresponds to a specific control signal that activates a particular component or operation in the processor. For example, a control word might have bits to:
- Select ALU operation (add, subtract, AND, OR).
- Control data transfer between registers.
- Generate memory read/write signals.
- Control program counter increment or load.
The sequence of control words executed forms the microprogram for a given machine instruction.
Programming the Basic Computer
To understand how a computer executes tasks, we need to delve into how it's programmed at its most fundamental levels: machine language and assembly language.
Machine Language
Machine language is the lowest-level programming language, consisting of binary or hexadecimal instructions that a computer's central processing unit (CPU) can directly understand and execute. Each instruction typically consists of an opcode (operation code) and operands.
- Opcode: Specifies the operation to be performed (e.g., add, move, jump).
- Operands: Specify the data or memory locations on which the operation is to be performed.
For example, a simplified machine instruction might look like:
0010 0101 11000001
Where 0010 could be the opcode for "ADD" and 0101 11000001 could be the operand representing a memory address.
Programming directly in machine language is extremely difficult, tedious, and error-prone due to its binary nature. It requires memorizing numerous codes and addresses.
Assembly Language
Assembly language is a low-level programming language that uses mnemonics (short abbreviations) to represent machine instructions, making it more human-readable than machine language. Each assembly language instruction typically corresponds to one machine language instruction.
For instance, the machine instruction 0010 0101 11000001 might be represented in assembly language as:
ADD R1, [1234H]
Here, ADD is the mnemonic for the addition operation, R1 might be a register, and [1234H] could be a memory address.
Assembly language still requires a deep understanding of the computer's hardware architecture, including registers, memory addressing modes, and instruction sets.
Assemblers
Since computers can only execute machine code, assembly language programs must be translated into machine code. This translation is performed by a program called an assembler.
The assembler reads the assembly language source code and converts each mnemonic instruction into its corresponding binary opcode and translates symbolic addresses into actual memory addresses.
Assembler Passes: Assemblers typically operate in multiple passes:
- Pass 1: The assembler scans the source code to build a symbol table. This table stores the names of labels (symbolic addresses) and their corresponding memory addresses. It also identifies and records any errors.
- Pass 2: The assembler uses the symbol table created in Pass 1 to translate the mnemonics and symbolic addresses into machine code. It generates the object code (machine code) and potentially other related files like a listing file.
Directives: Assemblers also support directives, which are instructions to the assembler itself, not to the CPU. Examples include defining data, reserving memory, and specifying the start of the program.
Loops
A loop is a fundamental programming construct that allows a sequence of instructions to be executed repeatedly. This is essential for tasks involving processing collections of data, performing repetitive calculations, or waiting for an event.
Loops are typically implemented using conditional branching instructions. A condition is checked, and if it's met, the program jumps back to the beginning of the loop's instruction sequence. If the condition is not met, the loop terminates, and the program continues with the instructions following the loop.
Types of Loops:
- Count-Controlled Loops: Execute a specific number of times. A counter variable is incremented or decremented in each iteration, and the loop terminates when the counter reaches a predefined limit.
- Condition-Controlled Loops: Continue executing as long as a certain condition remains true (or false). The condition is checked at the beginning or end of each iteration.
Example (Conceptual Assembly):
LOOP_START:
; Instructions to be repeated
; ...
DEC COUNTER ; Decrement the loop counter
JNZ LOOP_START ; Jump to LOOP_START if COUNTER is not zero
; Loop finishes here
In this example, DEC COUNTER decrements a counter, and JNZ LOOP_START (Jump if Not Zero) checks if the counter is zero. If it's not zero, it jumps back to LOOP_START, repeating the instructions.
Subroutines
A subroutine (also known as a procedure or function) is a block of code that performs a specific task and can be called from different parts of a program. Using subroutines promotes modularity, code reusability, and simplifies program structure.
Calling and Returning:
- CALL Instruction: When a subroutine is called, the processor typically executes a
CALLinstruction. This instruction saves the address of the instruction immediately following theCALL(the return address) onto a stack in memory. It then transfers control to the first instruction of the subroutine. - RET Instruction: At the end of the subroutine, a
RET(Return) instruction is executed. This instruction retrieves the return address from the stack and transfers control back to the calling program, resuming execution from where it left off.
Parameters and Return Values: Subroutines may need to accept input values (parameters) and return output values. These are often passed through registers or memory locations agreed upon by the caller and the subroutine.
Stack Usage: The stack is crucial for managing subroutine calls and returns. It stores return addresses, local variables, and parameters, allowing for nested subroutines (subroutines calling other subroutines).
CALL instruction is like saying "go do this task now," and RET is like saying "okay, task done, go back to what you were doing." The stack acts as a notepad to remember where to go back to.
I/O Programming
Input/Output (I/O) programming deals with how the CPU communicates with peripheral devices like keyboards, displays, printers, and storage devices. This communication involves transferring data between the CPU and these devices.
There are several methods for I/O programming:
- Programmed I/O (PIO): In PIO, the CPU directly controls the I/O operations. It repeatedly checks the status of the I/O device until it's ready for data transfer. The CPU then performs the data transfer. This method is simple but can be inefficient as the CPU spends a lot of time polling.
- Interrupt-Driven I/O: With interrupt-driven I/O, the I/O device signals the CPU when it's ready for a data transfer by sending an interrupt request. The CPU temporarily suspends its current task, handles the I/O request (by executing an Interrupt Service Routine - ISR), and then resumes its original task. This is more efficient than PIO as the CPU doesn't have to constantly check the device status.
- Direct Memory Access (DMA): DMA is an advanced technique where an I/O device can transfer data directly to or from main memory without involving the CPU in the actual data movement. A DMA controller handles the transfer. The CPU is only involved in initiating the transfer and being notified upon completion. This is the most efficient method for large data transfers.
I/O Addressing
There are two main ways to address I/O devices:
- Memory-Mapped I/O: I/O devices are treated as memory locations. They share the same address space as main memory. Special I/O instructions are not needed; standard memory access instructions (like
LOADandSTORE) are used to communicate with I/O devices. - Isolated I/O (Port-Mapped I/O): I/O devices have a separate address space from main memory. Special I/O instructions (e.g.,
INandOUT) are used to communicate with I/O devices, specifying both the device address and the I/O port number.
Example (Conceptual Isolated I/O):
; Write data to a printer (port 300H)
MOV AL, 'A' ; Load character 'A' into register AL
OUT 300H, AL ; Send the content of AL to I/O port 300H
; Read data from a keyboard (port 301H)
IN AL, 301H ; Read data from I/O port 301H into register AL
Basic Computer Architecture and I/O
A basic computer architecture typically includes dedicated I/O instructions. For example, a simple Instruction Set Architecture (ISA) might have instructions like:
IN port_address: Reads data from the specified I/O port into an accumulator register.OUT port_address: Writes data from the accumulator register to the specified I/O port.TEST_IO port_address: Checks the status of an I/O device.
When programming I/O, especially with programmed I/O, you often need to check device status flags. For instance, a keyboard might have a "data ready" flag, and a printer might have a "buffer empty" flag. The program must poll these flags before attempting to read or write data.
I/O Programming in Assembly
I/O programming in assembly language involves direct interaction with hardware ports. You'll use specific instructions to send commands to I/O devices and read data from them. This requires knowledge of the memory-mapped addresses or I/O port numbers assigned to each device.
For instance, to print a character using programmed I/O with isolated I/O:
- Check the printer's status port to see if it's ready to accept data.
- If not ready, loop back and check again (polling).
- If ready, write the character (from a register) to the printer's data port.
This low-level control allows for efficient management of peripherals, crucial in systems where resources are limited or performance is critical.