
A simple architecture for practical implementation of a central processing unit (CPU) is proposed as teaching resource, to experiment and verify the fundamental concepts of a simple computer architecture with its instruction set (in this case, 120 instructions). In this project, a foundry test is implemented with minimal memory resources. A block diagram for the proposed architecture is presented in the following figure:

The auxiliary modules are presented in the following figure:

To enable user access to both code and data memory spaces, before and after the execution of any program, the processing system includes a slave SPI communication block. The multiplexors are controlled by MODE signal to set the operation mode (programming/execution). Additionally, RUN signal is dedicated to start the code execution.
In practice, to work with the input and output ports of the Tiny Tapeout template, the entire system (with minimal memory) has been packaged in the Nano_sys_4Tiny module, which is represented schematically below:

In the Nano_sys_4Tiny module, the input bus OUT_CTRL is used to select the internal signal buses that will be available at the output ports OUT8B and OUT4B, according to the following table:

For mor details, see the HDL code Nano_mcsys_4Tiny_fit.v.
The proposed CPU uses a Harvard architecture in which program, data and IO are accessed from separate busses. The data word size is 16 bits while the code word size is 8 bits. The 16-bit ALU is a general-purpose arithmetic and logic unit and its result is always stored in the 32-bit R register. The arithmetic instructions affect the values of the following flags:
The architecture includes the following registers:

The CPU has 12-bit instruction pointer capable of addressing a 4k x 8 program. The reset vector is at 0x000, while the interrupt vectors are at 0xFF7, 0xFFA and 0xFFD, for Int2, Int1 and Int0, respectively.

The CPU can address a 256- level deep x 16-bit wide stack. The PUSH instruction first stores the data and later increment de SP. The POP instruction first decrements de SP and later restore the data. The user can access the stack space by means PUSH/POP instructions or with data transfers by means the USP pointer. The CALL instruction automatically pushes the IP in the stack and the RET instruction pops it to continue the program Flow. In the case of an interruption, if it is detected while running a program, the system automatically pushes the IP, R and F registers, while RETI instruction pops all these.
The CPU provides dedicated buses to access generic peripherals by means IN and OUT instructions. The user can add a required peripheral with this interface. In the current implementation, it has been reserved the first two locations of I/O space, i.e. 0x00 and 0x01, to control the 3 available interrupts.
The CPU has 3 external sources of interrupt, denoted as Int0, Int1 and Int2. The highest priority is assigned to Int0, while the lowest priority is assigned to Int2. If it is enabled, an interruption can be served while a program is executed, but also if the CPU is at stop status. When the interrupt service is required while a program is executed, it implies the usual context saving process. On the other hand, when the interrupt service is required while a program is stopped, it does not save the context. The registers to control these interrupts are mapped in the first two locations of the I/O space:

To enable an interruption, it is necessary to set the corresponding bit in IE register. When an interruption is enabled and it occurs (a positive edge is detected in the interrupt pin), the system automatically set the corresponding interruption flag in IF register and no other interrupt can be attended. Then, when the interrupt service routine finishes its task, before RETI instruction, the interrupt flag must be cleaned.
The proposed instruction set is can be conveniently codified with bytes. The following tables summarizes the proposed instruction codification:

The following is a demo program:

The CPU system must be programed before a code execution. The RAM and ROM loads are enabled with signal MODE=0. With signal MODE=1, the code execution starts with signal RUN=1.
To program the code space and access the data space, SPI transfers are used. The SPI word size is 32 bits with the following format:

The bit 31 (MSB) is set to specify a read operation. The address field specifies the location being accessed and de data field contains the information to be written or read. In the following figure, the SPI addressing map is presented:

For SPI transfers, the following specification is assumed:
In the case of a read sequence, the read command must be followed by a stuff SPI word (let's say, all ones or all zeros word). The read information will be in the data field of the SPI word transmitted by the slave SPI.
Within the slave_spi4nano module, the SCK frequency is assumed as CLK/8. For example, if the CLK frequency is 50 MHz, the SCK frequency is 6.25 MHz. SPI interface also works with low frequency, for example, if CLK frequency is 1525.879 Hz, the SCK frequency is 190.8 Hz. As a quick and practical reference for SPI signaling, although rough but illustrative, you can refer to the test code test.py.
As practical reference, a master SPI module, may be found in the following repository:
https://github.com/galaguna/Nano119-CPU-programmable-with-SPI
| # | Input | Output | Bidirectional |
|---|---|---|---|
| 0 | OUT_CTRL0 | OUT8B0 | OUT4B0 |
| 1 | OUT_CTRL1 | OUT8B1 | OUT4B1 |
| 2 | OUT_CTRL2 | OUT8B2 | OUT4B2 |
| 3 | SPI_SCK | OUT8B3 | OUT4B3 |
| 4 | SPI_MOSI | OUT8B4 | EINT0_SRC |
| 5 | SPI_CS | OUT8B5 | EINT1_SRC |
| 6 | RUN | OUT8B6 | EINT2_SRC |
| 7 | MODE | OUT8B7 | SPI_MISO |