F6 Simple Input/Output

You have already designed the sCPU in Logisim and executed the sequence summation program. To verify the execution results, you directly observed the values of the PC register and the GPRs. However, in an actual processor chip, it is extremely difficult to directly observe the internal state. In reality, users typically interact with processor chips via input/output (I/O).

Instruction Extension of sISA

The opcode of sISA still has one available slot. We can extend sISA by adding a new io instruction, enabling the sCPU to support input/output functionalities. Concurrently, to allow sISA to support more complex programs seamlessly, we need to enhance the functionality of the li and bner0 instructions. The finalized sISA encompasses the following four instructions:

 7  6  5  4  3   2  1   0
+--+--+--+--+---+--+--+--+
| 00 | rd | rs1 | rs2 | R[rd]=R[rs1]+R[rs2]
+--+--+--+--+---+--+--+--+
| 01 | rd |i/o|  idx  | R[rd]<=>dev[idx]
+--+--+--+--+---+--+--+--+
| 10 | rd | s |  imm  | R[rd]=imm << (s << 1)
+--+--+--+--+---+--+--+--+
| 11 |  offset  | rs2 | if (R[0]!=R[rs2]) PC=PC+sign_ext(offset)
+--+--+--+--+---+--+--+--+

Among them:

  • The behavior of the add instruction remains the same as before.
  • The io instruction performs input or output based on the direction bit i/o:
    • When i/o=0, it indicates an input operation, reading 8-bit data from the device numbered idx and writing it into R[rd].
    • When i/o=1, it indicates an output operation, sending the 8-bit data from R[rd] to the device numbered idx.
  • The li instruction differs from before; it utilizes a combination of s and imm to represent a larger immediate value. The << mentioned above denotes the left shift operator. If you are not entirely familiar with this operator, you can understand the behavior of the li instruction from another perspective: s indicates into which segment of the 8-bit data the 2-bit imm is placed, as detailed in the following table:
 7   6 5   4 3   2 1   0
+-----+-----+-----+-----+
| 00  | 00  | 00  | imm | s=00
+-----+-----+-----+-----+
| 00  | 00  | imm | 00  | s=01
+-----+-----+-----+-----+
| 00  | imm | 00  | 00  | s=10
+-----+-----+-----+-----+
| imm | 00  | 00  | 00  | s=11
+-----+-----+-----+-----+
  • The bner0 instruction differs from before; it uses the offset field to represent the jump target's offset relative to the PC, thereby supporting jumps to locations near the current PC value. Meanwhile, the offset field can represent negative numbers, allowing jumps to locations within the range of -8 to +7 relative to the current PC value.

Before adding the io instruction, let us first enhance the functionalities of the li and bner0 instructions. For the li instruction, we need to consider how to implement the functionality of the s field. In fact, we can regard the s field as a selection signal to choose one out of four immediate values, where each immediate value corresponds to the scenario of placing imm in a specific segment. From this perspective, the functionality of the li instruction can be implemented using a multiplexer.

For the bner0 instruction, since the offset field only represents the jump target's offset relative to the PC, the PC value must be added to the offset field to obtain the actual jump target. The addition operation can be implemented via an adder. Previously, an adder was already utilized to calculate PC+1; considering that calculating PC+1 is unnecessary when the bner0 instruction performs a jump, you can consider reusing this adder to compute the jump target. However, to reuse it, you need to consider how to add additional control signals.

Nevertheless, because the PC register has 8 bits while the offset field of the bner0 instruction has only 4 bits, the offset must first be extended to data matching the PC's bit-width before performing the addition. Generally, there are two extension methods. One is zero-extend, which always pads 0 to the high-order bits; the li instruction precisely requires zero-extension of the immediate value. The other is sign-extend, which pads the sign bit of the two's complement to the high-order bits.

In particular, we can also prove that before and after sign-extension, the true value of the two's complement remains consistent. Assume there is a -bit binary number . Sign-extending it to bits yields . If , the true values before and after extension are obviously identical. If , by expanding it according to the weighted positional value of the two's complement, we have:

It is easy to see that the above conclusion holds when . According to mathematical induction, the proposition is proven to be true.

F: Implement Enhanced Versions of Instructions

Modify the implementation of the sCPU to support the enhanced versions of the li and bner0 instructions.

Then, modify the instruction sequence of the sequence summation program so that the program uses the enhanced instructions to calculate 1+2+...+10. Have the sCPU execute the modified sequence summation program and check whether the summation result is correct.

Enabling sCPU to Support Input/Output

Now we can add some simple devices to the sCPU, then implement the io instruction, and finally allow the program to use the io instruction to access these devices.

LED

Instantiate an LED Bar component in Logisim; you can find it under the Input/Output category in the component library. After instantiation, adjust its properties: set the Input Format property to One Wire, and set the Segments property to 8. In this way, we obtain 8 LEDs driven by an 8-bit signal, where each bit of the signal can control the on/off state of the corresponding LED.

We hope that the program can use 8-bit data to control the LEDs. For example, using the calculation result of 1+2+...+10 to control the LEDs can achieve the effect of "outputting the calculation result to the LEDs." To this end, we need to add the io instruction; when the program executes the io instruction, the sCPU can use the specified data to control the LEDs.

Typically, there is more than one device, and different numbers are generally used to identify different devices. The program can specify which device to access through the idx field in the io instruction. We stipulate that when the io instruction is used for output, the number 000 represents the LED.

Furthermore, when using the io instruction for output, the rd field in the instruction needs to be read out as the source register number. However, it is not worthwhile to add a dedicated read port to the GPR specifically for this situation (such as adding raddr3 and rdata3). In fact, the io instruction does not use the rs1 and rs2 fields to access the GPR. Therefore, we can reuse the existing read ports of the GPR by adding correct control signals to select the rd field in the instruction as the address for one of the GPR's read ports.

Finally, we expect the on/off state of the LEDs to remain unchanged after the execution of the io instruction. If the GPR specified in the io instruction is directly connected to the LEDs, the on/off state of the LEDs will change when the value stored in the GPR changes next time, even if no io instruction is executed. This does not meet our expectations. To resolve this issue, we need to add an extra output data register for the LEDs. On the one hand, connect the output terminal of this output data register to the LEDs; on the other hand, have the io instruction write the data used to control the LEDs into this output data register. In this way, the value of the output data register can remain unchanged, thereby maintaining the on/off state of the LEDs.

Output the Calculation Result to the LEDs

Add the io instruction to the sCPU so that the io instruction can control the LEDs.

Then, modify the instruction sequence of the sequence summation program to allow the program to output the calculation result to the LEDs via the io instruction, and check whether the on/off status of the LEDs meets expectations.

Seven-Segment Display

The seven-segment display we used previously receives a 7-bit input signal, where each bit individually controls the on/off state of each segment. There is another type of seven-segment display that receives a 4-bit input signal and directly displays the hexadecimal digit corresponding to the 4-bit binary number; using it can save the decoding process.

Instantiate two Hex Digit Display components in Logisim; you can find them under the Input/Output category in the component library. After instantiation, adjust their properties: set the Has Decimal point: property to No. We can split an 8-bit signal into two 4-bit signals to individually drive the two seven-segment displays, which is equivalent to converting an 8-bit binary number into a 2-digit hexadecimal number for display. We stipulate that the number for this group of seven-segment displays in the io instruction is 001. We stipulate that when the io instruction is used for output, the number 001 represents this group of seven-segment displays.

Output the Calculation Result to the Seven-Segment Displays

Complete the implementation of the io instruction so that the io instruction can control the seven-segment displays. Similar to the LEDs, you also need to add an output data register for the seven-segment displays.

Then, modify the instruction sequence of the sequence summation program so that the program outputs the calculation result to the seven-segment displays via the io instruction, and check whether the display status of the seven-segment displays meets expectations. Note that the Hex Digit Display component displays in hexadecimal.

DIP Switch

Instantiate a Dip switch component in Logisim; you can find it under the Input/Output category in the component library. After instantiation, adjust its properties: set the Number of Switch property to 8. These 8 DIP switches can be combined into an 8-bit input signal. We stipulate that when the io instruction is used for input, the number 000 represents this group of DIP switches.

On the surface, although the device number for the DIP switches overlaps with the LEDs, because the DIP switch is an input device while the LED is an output device, the two can be further distinguished through the directional bit i/o in the io instruction.

Additionally, when the io instruction is used for input, the value of the corresponding device needs to be written into the rd register. You need to modify the data path and add the corresponding control signals to implement the input functionality.

Previously, the sequence summation program always calculated 1+2+...+10. If the last term of the sequence needed to be modified, the new last term had to be manually loaded into r0. In fact, we can set the last term on the DIP switches in advance, and then have the program read the last term from the DIP switches via the io instruction when execution begins, before proceeding with the calculation.

Unlike output devices such as LEDs, the state of an input device is set by the user and will not be affected by the internal state of the processor. Therefore, there is no need to add registers for input devices to temporarily store their states.

Read the Last Term of the Sequence from the DIP Switches

Complete the implementation of the io instruction so that the io instruction can read data from the DIP switches into the GPR.

Then, modify the instruction sequence of the sequence summation program so that the program reads the last term from the DIP switches via the io instruction, and check whether the calculation result meets expectations using the LEDs or the seven-segment displays.

The Situation Where the Last Term is 0

When the input last term is 0, what will the execution result of the program be? Why does this happen?

However, we do not require you to fix this issue.

Button

Instantiate 8 Button components in Logisim; you can find them under the Input/Output category in the component library. These 8 buttons can be combined into an 8-bit input signal, where a pressed button indicates the corresponding bit is 1, and a released button indicates the corresponding bit is 0. We stipulate that when the io instruction is used for input, the number 001 represents this group of buttons.

Control the Display of Results via Buttons

Complete the implementation of the io instruction so that the io instruction can read data from the buttons into the GPR.

Then, modify the instruction sequence of the sequence summation program so that during the loop, the current term is output to the LEDs. After the calculation is complete, it should continuously poll the button status until a button is pressed before the seven-segment displays show the calculation result. Check whether the button's behavior meets expectations.

Implement Step-by-Step Summation

Modify the instruction sequence of the sequence summation program to simultaneously achieve the following effects:

  1. In each loop, output the current term to the LEDs, and output the current summation result to the seven-segment displays.
  2. Use the button to control the progress of the loop; the program only enters the next loop each time the button representing the least significant bit is pressed.

That is, when running the program, the LEDs and seven-segment displays first show 1 and 1; after the user presses the button representing the least significant bit, the LEDs and seven-segment displays show 2 and 3; after the user presses the button representing the least significant bit, the LEDs and seven-segment displays show 3 and 6...

Because there are only 4 GPRs, and the offset range that the bner0 instruction can accommodate is only from -8 to 7, you may need to carefully design the allocation of the GPRs to minimize the number of instructions in the loop as much as possible, ensuring that the offset of the bner0 jump target does not exceed the representable range of the instruction.

Hint: There is a solution that requires only 12 instructions.

Implement Step-by-Step Summation (2)

You might find that when the user continuously presses and holds the button without releasing it, the progress of the loop will keep moving forward. If you want the program to enter the next loop only when the user presses and releases the button once, can you write a program that meets this requirement? Why?

Moving Towards Modern Processor Design

Congratulations, you have successfully designed a processor with some demonstration capabilities in Logisim. But at the same time, you should also feel that designing a processor in Logisim has quite a few drawbacks:

  • Cumbersome design. Although dragging components and wiring them together does give you the feeling of designing a circuit, these operations will become cumbersome as the scale of the design increases. The sCPU processor you have currently designed only has 4 instructions, but a processor with relatively complete functionalities typically has dozens of instructions; to implement a processor capable of booting a modern operating system, hundreds of instructions need to be implemented. If we consider mature commercial processors like modern Intel and ARM, they need to support thousands of instructions, the ISA manual alone is thousands of pages long, and the number of transistors on the chip reaches the scale of tens of billions.

  • Slow simulation speed. On the one hand, the simulation efficiency of Logisim itself is not high, and as the design scale becomes complex, the simulation speed will become slower and slower due to the increased number of components. On the other hand, for the same functionality of a program, when the ISA provides fewer types of instructions, the program typically uses more instructions to implement it, which will reduce execution efficiency by several times or even dozens of times.

  • Difficult debugging. As long as a single wire is accidentally connected incorrectly during the design process, the execution result of the program on the processor might not meet expectations. If the program scale is not large, we can still check the processor's execution state instruction by instruction to see if it is consistent with the ISA's state; when the program needs to execute thousands or tens of thousands of instructions, finding out which instruction's execution did not meet expectations is extremely difficult. If you were to run a real application like a game, the number of instructions executed would be an astronomical figure! Finding an error among so many instructions is simply harder than finding a needle in a haystack!

These issues all indicate that designing a processor using Logisim is not a highly scalable solution. In fact, modern processor design flows primarily adopt a code development approach, using a Hardware Description Language (HDL) to describe how hardware components are connected, thereby providing the logic structure of the processor without the need for manual wiring operations. After the code development is completed, simulation tools are needed to check whether the logic structure provided by the code meets expectations. Furthermore, EDA tools are required to transform the code into a layout, just as a compiler transforms C code into an instruction sequence.

In addition to the steps mentioned above, modern processor design flows also encompass more stages, as shown in the figure below. We list a portion of the problems that need to be solved in the modern processor design flow:

  • Architecture Design: Given a new feature (which could be functionalities from the ISA specification like adding new instructions, or functional optimization schemes at the processor level), how to propose a design scheme and decompose it into appropriate hardware modules for implementation?
  • Logic Design: With a design scheme, how to implement the hardware modules in the design scheme at the circuit level using HDL?
  • Functional Verification: How to verify that the circuit described by the HDL satisfies the expected functionality of the new feature?
  • Performance Verification: How to ensure that the performance of the processor meets expectations?
  • Circuit Evaluation: How to evaluate and optimize the processor's metrics such as frequency, area, and power consumption?
  • Physical Design: How to transform the HDL code into a tape-out-ready layout?
  • Performance Optimization: How to discover and locate performance bottlenecks in the processor, and design corresponding optimization schemes?

modern processor design flows

At the same time, you should also realize that even in the Logisim design process mentioned above, there are still many steps you might not currently understand, such as:

  • How to efficiently obtain the instruction sequence of a program?
  • How to develop more programs to run on the processor?

Processor Design != HDL Coding

Many students majoring in electronics-related fields might simply view processor design as HDL coding work. This view is one-sided: judging from the figure above, HDL coding is only the work of the logic design stage, whereas the entire flow consists of numerous stages. In fact, many stages in the processor design process are related to software, because:

  • A processor cannot work without software. You already know that the working principle of a processor is to continuously execute instructions, and these instructions are software. To evaluate a processor is to see whether software runs correctly and well on this processor.

  • In the flow shown in the figure above, completing these steps requires the support of various tools and infrastructure. The essence of these tools and infrastructure is also software, especially those tools closely related to processor functionalities (such as the instruction set simulator, functional simulator, and differential testing methods in the figure); they play an important role in carrying out the relevant steps.

  • Although HDL code describes hardware, as code itself, it is also a type of software. Since it is code, appropriate software technologies must be used to manage, maintain, test, evaluate, and optimize it. Especially as the code scale increases, these issues will become increasingly important. Fortunately, the software engineering field has been researching these issues for decades; when necessary, we can draw on the experience of the software engineering field to help us solve related problems.

In short, to design a good processor, one must value the role that software plays in it.

Currently, you might not fully understand the significance of the aforementioned problems, nor do we intend to elaborate on them here. Moving forward, we will abandon the Logisim design approach and guide everyone to gradually build the modern processor design flow mentioned above, and your understanding of these problems will also become increasingly clear during this process.

You are about to step into the world of code. Are you ready?

zxz