Skip to content

A small and simple 8-bit CPU built in Logisim. The project also includes an assembler and a manual for those who want to learn how a processor works.

License

Notifications You must be signed in to change notification settings

pescetti-studio/Flip01-CPU

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flip01: a simple yet versatile 8-bit CPU

Overview

Flip01, short for First Level Instructional Processor, is a small 8-bit CPU with a 16-bit address bus.
This means it can handle data ranging from 0 to 2⁸–1 (255) and can store up to 2¹⁶ (65536 bit ~ 64 KB) different pieces of data.

Data and addresses travel on two separate connection networks called the data bus and address bus, respectively.

2

The schematic datapath of Flip01: it shows the connections between the components and the two buses (Data and Address).

Flip01 is based on a Harvard architecture, meaning that data and instructions (the actions the processor must execute) are stored in two separate memories: MEM1 for data and MEM2 for instructions.
MEM1 is an 8-bit memory with a capacity of 64 KB, while MEM2 is a 9-bit memory with a capacity of 64 KB.

3

Schematic representation of the features of MEM1 and MEM2 and their connections to the rest of the circuit.

To move, temporarily store, and possibly modify these data, a component called register is used.

REGISTERS

In very simple terms, a register can be described as a standalone memory cell with fast read and write capabilities.
In Flip01, there are 10 main registers (+ 1: spoiler), and their size and connection (data or address bus) are defined by their specific function.

  • AX: This is the first of two general-purpose registers.
    This means it has no specific function within the processor but is used to store data for calculations.
    Since it only handles data, it is 8 bits in size and is connected solely to the data bus.
  • BX: The second and final general-purpose register, with the same properties as AX.
    Like AX, it is 8 bits and connected only to the data bus.
  • STAUTSAX (SAX): An 8-bit register designed to save the contents of AX when needed.
    It can only communicate with AX, so it is not directly connected to either the data bus or the address bus.
  • STATUSBX (SBX): A mirror register to STATUSAX.
    It is also an 8-bit register but saves the contents of BX when required.
    Like SAX, it is not connected directly to the data or address buses.
  • DATA REGISTER (DR): This register communicates directly with the memory holding data (MEM1).
    Since it handles both data and addresses, it is a 16-bit register connected to both the data and address buses.
    It uses converters to adapt the size before input and output:
    • On input, data is converted from 8 to 16 bits by padding with zeros.
    • On output, data is converted back from 16 to 8 bits by discarding the first 8 bits.
      If any discarded bits contain data, an error LED will light up as a preventive measure.
  • INSTRUCTION REGISTER (IR): A mirror register to DR, but it interfaces with the memory that holds instructions (MEM2).
    Instructions in memory are 9 bits long, divided as follows:
    The first bit specifies which general-purpose register (AX or BX) the instruction refers to:
    • 1 for AX, 0 for BX.
    • The remaining 8 bits contain the actual instruction.
      Only the 8 bits representing the instruction are loaded into the IR, so it is an 8-bit register.
  • ALUIN: Inside the processor, there is a unit responsible for performing calculations, called the ALU.
    The ALUIN register takes and stores the first of the two operands for the ALU.
    This is necessary because both operands cannot enter at the same time (they would conflict on the data bus).
    This register holds the first operand until the second arrives.
    The second operand doesn’t need to be stored since the ALU processes it immediately upon arrival.
    This register is also 8 bits, as it only handles data.
  • ALUOUT: This register stores the result of the ALU operation, then passes it to one of the general-purpose registers if needed for future operations.
    ALUOUT is also an 8-bit register.
  • MEMORY ADDRESS REGISTER (MAR): This register stores the memory address of the data to be read or rewritten.
    It is useful when an instruction refers to reading data from a specific memory location, which is different from the instruction’s own location.
    MAR helps retrieve the address of the data, locate it in memory, and either read its value or overwrite it with a new one.
    Since it deals only with addresses, it is a 16-bit register connected to the address bus both for input and output.
    It is also connected to the data bus for output, with a 16-to-8-bit converter (similar to the one in DR) that triggers an error LED if data is lost during conversion.
  • PROGRAM COUNTER (PC): This register holds the address of the next instruction to be executed.
    It ensures that, at each step of the program, the processor knows exactly where to read the next instruction.
    This is particularly useful when the execution flow is non-linear.
    (foreshadowing jump instructions????)
    Like MAR, the PC is a 16-bit register connected to the address bus for both input and output and to the data bus for output, with an intermediary converter.

All registers update when there is a state change in a common signal that goes to all components that require it.
This signal is called the clock, and ideally, it looks like this:

4

A diagram showing the behavior of the clock signal over time.

It oscillates between 0 and 1, and the instant change from 0 to 1 updates the values stored in the registers based on the data circulating in the bus at that exact moment.

If registers are used to temporarily store data, as previously mentioned, another fundamental component called the ALU (Arithmetic Logic Unit) is used to modify it.

ALU

Every processor has a different ALU in terms of calculation power or data handling capabilities.
The ALU of Flip01 is designed to take two 8-bit inputs and produce a single output of the same size.

Based on the configuration and the combination of 6 binary control signals (0 or 1), the ALU can perform 7 main operations and 4 derived ones.
The 6 control signals are as follows:

  • BRin: Sets the second input of the ALU to 0.
  • s0, s1, s2: The unique combinations of these signals determine which of the 7 main operations will be executed.
  • Cin, op: These signals control additional bits for operations that require them.

The 7 main operations are:
  • ADD: When given two inputs, A and B, this configuration outputs the sum of the two: A + B
  • SUB: With the same inputs, A and B, this configuration outputs the difference: A — B
  • AND: For the same inputs, this configuration performs a bitwise AND operation on A and B: A AND B
    Following the logical structure of a single-bit AND operator.

5

Logic table of the AND operator (left) and two examples with 8-bit operands (right)

  • OR: For the same inputs, this configuration performs a bitwise OR operation on A and B: A OR B
    Following the logical structure of a single-bit OR operator.

6

Logic table of the OR operator (left) and two examples with 8-bit operands (right)

  • NOT: In this case, only one operand is considered as input (A).
    This configuration outputs the bitwise inverse of the input operand: NOT A
    Following the logical structure of a NOT operator

7

Logic table of the NOT operator (left) and two examples with 8-bit operands (right)

  • SHIFTL: This configuration shifts the bits of operand A to the left by B positions, keeping the result 8 bits long.
    The vacated positions on the right are filled with 0 padding.
  • SHIFTR: This configuration shifts the bits of operand A to the right by B positions, keeping the result 8 bits long.
    The vacated positions on the left are filled with 0 padding, regardless of the leftmost bit (logical shift).

8

Practical example of how left shift (shiftl) and right shift (shiftr) operations work.

10

Control signal combinations that define the various main operations indicated.

By manipulating the ALU’s inputs, you can derive four additional operations:

  • INC: The first input to the ALU remains unchanged, while the second is forced to 1.
    Setting the ALU to the add configuration gives A + 1, incrementing the value by one unit.
  • DEC: Similarly, the first input remains unchanged, while the second is forced to 1.
    However, setting the ALU to the sub configuration gives A — 1, decrementing the value by one unit.
  • 0: Regardless of the input, this operation outputs the value 0.
    Given input A, forcing the second input to 0 and performing an AND operation results in 0, no matter the value of A.
  • A: Finally, you can output the same value as the first input without modifying it.
    Given input A, forcing the second input to 0 and performing a sub operation results in A: A — 0 = A.

11

Control signal combinations that define the four additional operations.

Connected to the ALU there is an additional, critical register:

FLAG REGISTER

This is the third and final register that communicates with the ALU.

It’s an 8-bit register where each bit indicates a specific aspect of the most recent operation:

  • X (Shift Error): This bit is set if the number of bits to be shifted exceeds 8, surpassing the maximum length of the operand involved in the shift operation.
    It can be ignored if the executed operation is of a different type.
  • L (A < B): This bit is set if operand A is less than operand B in absolute value.
  • E (A = B): This bit is set if operand A is equal to operand B in absolute value.
  • G (A > B): This bit is set if operand A is greater than operand B in absolute value.
  • C (Carry): This bit is set if there was a carry during the operation, meaning the most significant bit had to generate a carry to the previous bit.
  • V (Overflow): This bit is set if the result exceeds the 8-bit limit, even though both operands are 8 bits.
    This indicates that an extra bit would have been required for the result to be correct.
  • Z (Zero): This bit is set if the result of the operation is zero.
  • N (Negative): This bit is set if the result of the operation is negative for any reason.

Tip

Quick reminder before we continue: if you enjoy these kinds of projects, feel free to follow us FOR FREE on Patreon! You’ll get early updates and exclusive content, and even a “simple” follow really helps keep these projects free and open source. Thanks :)

INSTRUCTIONS

Instructions represent the full range of actions the processor can perform.
Flip01 has 39 instructions, each identified by a unique operation code (op-code) in hexadecimal format.

Instructions consist of micro-instructions, which are movements of data between registers or arithmetic and logical operations.
For example, the addR instruction, which adds the contents of the two general-purpose registers AX and BX, is made up of the following micro-instructions:

12

Example of reading and interpreting microinstructions (using the microinstructions of the add$ instruction as a case study).

If two micro-instructions operate on different buses (data and address) and are sequential, they can be executed in the same clock cycle, as no data will conflict.

In MEM1 memory cells, it’s possible to assign a unique name to each cell, allowing access to it during program execution through read and write actions.
To assign a name to a memory cell, the following syntax is used:

[name] = [value]

The = operator symbolizes an assignment operation.

For the remainder of the manual, this type of memory cell will be referred to as a VARIABLE, adopting standard terminology.

In textual form, this can be expressed as:
“The cell named [name] will initially contain the value [value] at the start of the program.”

The address of the memory cell, or its location within MEM1, is assigned procedurally, typically as the first available cell after those reserved for program parameters.

The value indicated represents only the initial assignment to the cell, and to modify its content later, you must use the CPU instructions.

Currently, the instructions available on Flip01 can be grouped into five categories:

  1. Direct addressing instructions
  2. Single-operand instructions
  3. Zero-operand instructions
  4. Immediate instructions
  5. Jump instructions

Direct Addressing Instructions

These instructions consist of two parameters: the first is the general-purpose register (AX or BX) they refer to, and the second is the memory address containing the value to be considered.
In Flip01’s high-level syntax, the memory address specified as the second parameter is associated with the unique name of a variable.
These instructions involve memory access for reading the data.

Instruction syntax: Instruction [register] [variable]

load

(syntax: load [register] [variable])
op-code: 0x02
This instruction copies the value of the variable specified in the [variable] parameter into the register indicated in the [register] parameter.

  • MEM1 [variable] -> DR
  • DR -> [register]

The load instruction requires 2 clock cycles to execute.

store

(syntax: store [register] [variable])
op-code: 0x04
This instruction copies the value in the [register] parameter into the memory cell assigned to the variable specified in the [variable] parameter.

  • [register] -> DR
  • DR -> MEM1 [variable]

The store instruction requires 2 clock cycles to execute.

add

(syntax: add [register] [variable])
op-code: 0x06
This instruction adds the value associated with the [variable] parameter to the value stored in the [register] parameter. The result is saved in the specified register.
[register] = [register] + [variable]

  • MEM1 [variable] -> DR, [register] -> ALUA
  • DR + ALUA -> ALUOUT
  • ALUOUT -> [register]

The add instruction requires 3 clock cycles to execute.

sub

(syntax: sub [register] [variable])
op-code: 0x09
This instruction subtracts the value associated with the [variable] parameter from the value stored in the [register] parameter. The result is saved in the specified register.
[register] = [register][variable]

  • MEM1 [variable] -> DR, [register] -> ALUA
  • DR — ALUA -> ALUOUT
  • ALUOUT -> [register]

The sub instruction requires 3 clock cycles to execute.

and

(syntax: and [register] [variable])
op-code: 0x0C
This instruction performs a bitwise AND operation between the value stored in the [register] and the value associated with the [variable]. The result is saved in the specified register.
[register] = [register] AND [variable]

  • MEM1 [variable] -> DR, [register] -> ALUA
  • DR AND ALUA -> ALUOUT
  • ALUOUT -> [register]

The and instruction requires 3 clock cycles to execute.

or

(syntax: or [register] [variable])
op-code: 0x0F
This instruction performs a bitwise OR operation between the value stored in the [register] and the value associated with the [variable]. The result is saved in the specified register.
[register] = [register] OR [variable]

  • MEM1 [variable] -> DR, [register] -> ALUA
  • DR OR ALUA -> ALUOUT
  • ALUOUT -> [register]

The or instruction requires 3 clock cycles to execute.

cmp

(syntax: cmp [register] [variable])
op-code: 0x30
The CMP instruction compares the value in the register specified in the [register] field with the value of the variable indicated in the [variable] field.
The flags are updated upon completion of this operation.

  • MEM1 [variable] -> DR, [register] -> ALUA
  • ALUA — DR -> ALUOUT

The cmp instruction requires 2 clock cycles to execute.

Single-Operand Instructions

These instructions involve only one parameter, usually the general-purpose register (AX or BX) they refer to.

Instruction syntax: Instruction [register]

not

(syntax: not [register])
op-code: 0x12
This instruction inverts the bits of the value stored in the [register]. The result is saved in the specified register.
[register] = NOT [register]

  • DR -> ALUA
  • ALUA — [register] -> ALUOUT
  • ALUOUT -> [register]

The not instruction requires 3 clock cycles to execute.

neg

(syntax: neg [register])
op-code: 0x15
This instruction negates the value stored in the [register]. The result is saved in the specified register.
[register] = NEG [register]

  • DR -> ALUA
  • ALUA — [register] + 1 -> ALUOUT
  • ALUOUT -> [register]

The neg instruction requires 3 clock cycles to execute.

inc

(syntax: inc [register])
op-code: 0x24
This instruction increments the value stored in the [register] by 1. The result is saved in the specified register.
[register] = [register] + 1

  • [register] -> ALUA
  • ALUA + 1 -> ALUOUT
  • ALUOUT -> [register]

The inc instruction requires 3 clock cycles to execute.

dec

(syntax: dec [register])
op-code: 0x27
This instruction decrements the value stored in the [register] by 1. The result is saved in the specified register.
[register] = [register] — 1

  • [register] -> ALUA
  • ALUA — 1 -> ALUOUT
  • ALUOUT -> [register]

The dec instruction requires 3 clock cycles to execute.

rnd

(syntax: rnd [register])
op-code: 0x37
This instruction stores a random value between 0 and 255 in the [register].

  • RND-> [register]

The rnd instruction requires 1 clock cycle to execute.

save

(syntax: save [register])
op-code: 0x5C
This instruction copies the value from the [register] into the corresponding status register.

  • [register] -> STATUS [register]

The save instruction requires 1 clock cycle to execute.

read

(syntax: read [register])
op-code: 0x5D
This instruction copies the value from the corresponding status register into the [register].

  • STATUS [register] -> [register]

The read instruction requires 1 clock cycle to execute.

Zero-Operand Instructions

These instructions do not take any parameters.
They primarily act on the processor state or perform operations between the two general-purpose registers, without needing to specify a target.

Instruction syntax: Instruction

addR

(syntax: addR)
op-code: 0x18
This instruction adds the values contained in the two general-purpose registers (AX and BX). The result is stored in register AX.
AX = AX + BX

  • AX -> ALUA
  • ALUA + BX -> ALUOUT
  • ALUOUT -> AX

The addR instruction requires 3 clock cycles to execute.

subR

(syntax: subR)
op-code: 0x1E
This instruction subtracts the value in register BX from the value in register AX. The result is stored in register AX.
AX = AX — BX

  • AX -> ALUA
  • ALUA — BX -> ALUOUT
  • ALUOUT -> AX

The subR instruction requires 3 clock cycles to execute.

andR

(syntax: andR)
op-code: 0x21
This instruction performs a bitwise AND operation between the values stored in the general-purpose registers. The result is stored in register AX.
AX = AX AND BX

  • AX -> ALUA
  • ALUA AND BX -> ALUOUT
  • ALUOUT -> AX

The andR instruction requires 3 clock cycles to execute.

orR

(syntax: orR)
op-code: 0x1B
This instruction performs a bitwise OR operation between the values stored in the general-purpose registers. The result is stored in register AX.
AX = AX OR BX

  • AX -> ALUA
  • ALUA OR BX -> ALUOUT
  • ALUOUT -> AX

The orR instruction requires 3 clock cycles to execute.

cmpR

(syntax: cmpR)
op-code: 0x32
This instruction compares the values stored in the general-purpose registers. The comparison is performed by subtracting the value in register BX from the value in register AX, updating the Flag register accordingly, and discarding the result.

  • AX -> ALUA
  • ALUA — BX -> ALUOUT

The cmpR instruction requires 2 clock cycles to execute.

flip

(syntax: flip)
op-code: 0x34
This instruction swaps the values stored in the two general-purpose registers (AX and BX).

  • AX -> ALUA
  • BX -> AX, ALUA-> ALUOUT

The flip instruction requires 2 clock cycles to execute.

pause

(syntax: pause)
op-code: 0x38
This instruction halts the execution of the program.

  • CLKDIS = 1
  • (wait)

The pause instruction requires 2 clock cycles to execute.

Immediate Instructions

These instructions consist of two parameters: the first is the general-purpose register (AX or BX) they refer to, and the second is the constant value to be considered.

Instruction syntax: Instruction [register] [value]

Unlike direct instructions, these instructions do not require memory access to read the data.

load$

(syntax: load$ [register] [value])
op-code: 0x59
This instruction copies the [value] parameter into the [register]. It is a load instruction.

  • DR -> [register]

The load$ instruction requires 1 clock cycle to execute.

add$

(syntax: add$ [register] [value])
op-code: 0x50
This instruction adds the [value] parameter to the value stored in the [register]. The result is saved in the specified register.
[register] = [register] + [value]

  • [register] -> ALUA
  • ALUA + DR -> ALUOUT
  • ALUOUT -> [register]

The add$ instruction requires 3 clock cycles to execute.

sub$

(syntax: sub$ [register] [value])
op-code: 0x4D
This instruction subtracts the [value] parameter from the value stored in the [register]. The result is saved in the specified register.
[register] = [register][value]

  • [register] -> ALUA
  • ALUA — DR -> ALUOUT
  • ALUOUT -> [register]

The sub$ instruction requires 3 clock cycles to execute.

and$

(syntax: and$ [register] [value])
op-code: 0x53
This instruction performs a bitwise AND operation between the value stored in the [register] and the [value] parameter. The result is saved in the specified register.
[register] = [register] AND [value]

  • [register] -> ALUA
  • ALUA AND DR -> ALUOUT
  • ALUOUT -> [register]

The and$ instruction requires 3 clock cycles to execute.

or$

(syntax: or$ [register] [value])
op-code: 0x56
This instruction performs a bitwise OR operation between the value stored in the [register] and the [value] parameter. The result is saved in the specified register.
[register] = [register] OR [value]

  • [register] -> ALUA
  • ALUA OR DR -> ALUOUT
  • ALUOUT -> [register]

The or$ instruction requires 3 clock cycles to execute.

shiftl$

(syntax: shiftl$ [register] [value])
op-code: 0x2A
This instruction performs a logical left shift on the value stored in the [register] by the number of bits specified in the [value] parameter. The result is saved in the specified register.
[register] = [register] shiftl [value]

  • [register] -> ALUA
  • ALUA SHIFTL DR -> ALUOUT
  • ALUOUT -> [register]

The shiftl$ instruction requires 3 clock cycles to execute.

shiftr$

(syntax: shiftr$ [register] [value])
op-code: 0x2D
This instruction performs a logical right shift on the value stored in the [register] by the number of bits specified in the [value] parameter. The result is saved in the specified register.
[register] = [register] shiftr [value]

  • [register] -> ALUA
  • ALUA SHIFTR DR -> ALUOUT
  • ALUOUT -> [register]

The shiftr$ instruction requires 3 clock cycles to execute.

cmp$

(syntax: cmp$ [register] [value])
op-code: 0x5A
This instruction compares the value stored in the [register] with the [value] parameter.
The comparison is performed by subtracting the [value] from the register’s value, updating the Flag register accordingly, and discarding the result.

  • [register] -> ALUA
  • ALUA OR DR -> ALUOUT

The cmp$ instruction requires 2 clock cycles to execute.

Jump Instructions

These instructions interrupt the linear execution of the program to execute code segments identified by labels.

Instruction syntax: Instruction [label]

jmp

(syntax: jmp [label])
op-code: 0x4C
This instruction unconditionally jumps to the specified [label].

  • DR -> PC

The jmp instruction requires 1 clock cycle to execute.

jc

(syntax: jc [label])
op-code: 0x3A
This instruction jumps to the specified [label] if the carry bit © is set to 1 by the previous instruction.

  • DR -> PC

The jc instruction requires 1 clock cycle to execute.

jv

(syntax: jv [label])
op-code: 0x3C
This instruction jumps to the specified [label] if the overflow bit (V) is set to 1 by the previous instruction.

  • DR -> PC

The jv instruction requires 1 clock cycle to execute.

jn

(syntax: jn [label])
op-code: 0x3E
This instruction jumps to the specified [label] if the negative bit (N) is set to 1 by the previous instruction.

  • DR -> PC

The jn instruction requires 1 clock cycle to execute.

jz

(syntax: jz [label])
op-code: 0x40
This instruction jumps to the specified [label] if the zero bit (Z) is set to 1 by the previous instruction.

  • DR -> PC

The jz instruction requires 1 clock cycle to execute.

je

(syntax: je [label])
op-code: 0x42
⚠ This instruction must immediately follow a compare instruction (cmp, cmpR, cmp$).
It jumps to the specified [label] if and only if the operands specified by the previous compare instruction are equal; otherwise, the program continues normally.

  • DR -> PC

The je instruction requires 1 clock cycle to execute.

jg

(syntax: jg [label])
op-code: 0x44
⚠ This instruction must immediately follow a compare instruction (cmp, cmpR, cmp$).
It jumps to the specified [label] if and only if the first operand in the previous compare instruction is greater than the second operand; otherwise, the program continues normally.

  • DR -> PC

The jg instruction requires 1 clock cycle to execute.

jl

(syntax: jl [label])
op-code: 0x46
⚠ This instruction must immediately follow a compare instruction (cmp, cmpR, cmp$).
It jumps to the specified [label] if and only if the first operand in the previous compare instruction is less than the second operand; otherwise, the program continues normally.

  • DR -> PC

The jl instruction requires 1 clock cycle to execute.

jle

(syntax: jle [label])
op-code: 0x48
⚠ This instruction must immediately follow a compare instruction (cmp, cmpR, cmp$).
It jumps to the specified [label] if and only if the first operand in the previous compare instruction is less than or equal to the second operand; otherwise, the program continues normally.

  • DR -> PC

The jle instruction requires 1 clock cycle to execute.

jge

(syntax: jge [label])
op-code: 0x4A
⚠ This instruction must immediately follow a compare instruction (cmp, cmpR, cmp$).
It jumps to the specified [label] if and only if the first operand in the previous compare instruction is greater than or equal to the second operand; otherwise, the program continues normally.

  • DR -> PC

The jge instruction requires 1 clock cycle to execute.

A processor must also be capable of receiving data from external sources, processing it, and displaying it to the user.
To accomplish this, Flip01 utilizes an external circuit called the Input & Output Manager, or simply the:

I/O Manager

This circuit is responsible for reading from and writing to 64 unique I/O devices.
Split into 32 input devices and 32 output devices, each port has an identifier ranging from 0 to 31.
During the program, the user will decide which port to connect their devices to and assign them an identifier number accordingly.

In the demo version of Flip01 on Logisim, both in the “Flip01_Full” circuit and the “Playground” version (which features a compact processor design to facilitate external connections), the I/O Management module already has devices connected in specific positions, with ports already assigned.

input

  • Port I00 — Keyboard Data [7 bits]
  • Port I01 — Keyboard Ready: Indicates if the keyboard buffer contains a character [1 bit]
  • Port I23 — BTN0: A push-button (1/8) [1 bit]
  • Port I24 — BTN1: A push-button (2/8) [1 bit]
  • Port I25 — BTN2: A push-button (3/8) [1 bit]
  • Port I26 — BTN3: A push-button (4/8) [1 bit]
  • Port I27 — BTN4: A push-button (5/8) [1 bit]
  • Port I28 — BTN5: A push-button (6/8) [1 bit]
  • Port I29 — BTN6: A push-button (7/8) [1 bit]
  • Port I30 — BTN7: A push-button (8/8) [1 bit]

The 21 input ports from I02 to I22 are unassigned and can be freely used by the user.

output

  • Port O00 — TTY Data (Teletypewriter, capable of displaying text messages) [7 bits]
  • Port O01 — RGB Video X Coordinate [8 bits]
  • Port O02 — RGB Video Y Coordinate [8 bits]
  • Port O03 — RGB Video Write Enable: When set to 1, it writes to the cursor position defined by X and Y [1 bit]
  • Port O04 — LED State 0: Turns on if it receives 1, and stays on until it receives 2, and vice versa (1/4) [2 bits]
  • Port O05 — LED State 1: Turns on if it receives 1, and stays on until it receives 2, and vice versa (2/4) [2 bits]
  • Port O06 — LED State 2: Turns on if it receives 1, and stays on until it receives 2, and vice versa (3/4) [2 bits]
  • Port O07 — LED State 3: Turns on if it receives 1, and stays on until it receives 2, and vice versa (4/4) [2 bits]
  • Port O23 — LED Matrix 8x8 Row 0 (1/8) [8 bits]
  • Port O24 — LED Matrix 8x8 Row 1 (2/8) [8 bits]
  • Port O25 — LED Matrix 8x8 Row 2 (3/8) [8 bits]
  • Port O26 — LED Matrix 8x8 Row 3 (4/8) [8 bits]
  • Port O27 — LED Matrix 8x8 Row 4 (5/8) [8 bits]
  • Port O28 — LED Matrix 8x8 Row 5 (6/8) [8 bits]
  • Port O29 — LED Matrix 8x8 Row 6 (7/8) [8 bits]
  • Port O30 — LED Matrix 8x8 Row 7 (8/8) [8 bits]
  • Port O31 — Reset Bit: When set to 1, it resets the connected device.
    If multiple devices are in use, it might be helpful to assign different reset bits to reset devices at different times.

The 15 output ports from O08 to O22 are unassigned and can be freely used by the user.

If you need to change the reference ports for certain components, you will have to update the corresponding port entered as a constant within the module.
The port inside the module must match the selected port.
This step is also crucial if additional components of the same type are added.
The devices that require this change are:

  • The 8x8 LED matrix controller: Each row corresponds to an output, meaning each port must be synchronized with the I/O Manager.
  • The RGB video controller: The X and Y coordinate inputs correspond to two ports that also need to be synchronized with the I/O Manager.

I/O Instructions

These instructions handle the communication between external devices and the CPU.
They require two parameters: the first is the register that will be affected by the instruction, while the second is the name of the device that will interact with that register.

Instruction syntax: Instruction [register][device]

input

(syntax: input [register] [device])
op-code: 0x5E
This instruction reads the value transmitted from the input device [device] connected to the port [port number] and copies it into the specified register [register].
Each input device must be declared using the syntax:
> [device] [port number]

  • DR -> I/O Manager, [device] > [register]

The input instruction requires 1 clock cycle to execute.

output

(syntax: output [register] [device])
op-code: 0x5F
This instruction reads the value stored in the specified register [register] and transmits it to the output device [device] connected to the port [port number].
Each output device must be declared using the syntax:
< [device] [port number]

  • DR -> I/O Manager, [register] > [device]

The output instruction requires 1 clock cycle to execute.

Writing programs and manually replacing op-codes, hexadecimal values, and addresses can be tiresome, so we have created a small assembler, which acts as a translator from high-level language.

THE ASSEMBLER

This program is responsible for translating mnemonic instructions (load, sub, add$, etc.) into their corresponding opcodes, while also converting numeric values and memory references into formats understandable by the processor.

4

The assembler after startup

The code should be written in the panel that, on startup, displays the message “write your text here”.
The conversion is automatically shown in the space below.

To disable the live machine code conversion, simply press the corresponding button labeled Live analysis enabled.
At that point, the button and interface will indicate that live translation has been paused.

To resume real-time conversion or to view the corresponding translation, press the button again.

To:

  • open a text file (.txt) containing code you want to analyze
  • save the current file
  • clear the content
  • start a new file

just click the corresponding buttons.

After specifying the name or the path for the current file, it won’t be necessary to do so again, and all subsequent saves will be made to that file.
This continues until the user initializes a new file via the New Code button.

The assembler comes with a built-in decimal-hexadecimal-binary converter.
To access it, just click the Conversion Table button, which opens the converter in a new window.
Simply select the input format, enter the value in the text bar, and the conversions into all three formats will happen automatically.
A control panel below the results displays any input errors or format issues in real time.

6

The converter

Note

Closing the converter window won’t affect the assembler, but closing the assembler will also close the converter window if it’s open.

Another handy feature of the assembler is the View Manual button.
As the name suggests, it opens the free Flip01 manual in your default browser.
The manual is incredibly useful for programming, as it includes a full description of the processor, along with opcodes, details, and explanations of all supported CPU instructions.

The manual is a visually enhanced and easier-to-read version of the information found in this README.
It’s the same free manual available on Medium, which has been previously mentioned.

Any changes made in the assembler’s text editor are automatically saved in a log.txt file located in the same folder as the assembler.
If the assembler crashes for any reason, your unsaved work won’t be lost.
You can recover it by opening the log.txt file directly from the assembler.
Think of it as a real-time safety net, creating backup files on the fly to safeguard your progress in case something goes wrong.

To add a comment to the code, use the following syntax:

// Comment

Comments can only be added at the beginning of a new line.

Note

The assembler was developed in C/C++ using the Dev C++ IDE.

We hope that the UI and UX are quite intuitive, but it may be helpful to explicitly outline all possible errors, their corresponding codes, and how to resolve them:

Errors

  • E — 000: The file you are trying to open and read using the designated button may be corrupted or unreachable.
    Please check that the file is intact and in the correct format (.txt).
  • E — 001: The code you have written is too large for Flip01!
    Try to shorten it and clean it up by using calls to labels for repeated code segments.
  • E — 002: The instruction you have written is not among those supported by Flip01.
    Double-check that the syntax is correct and remember that the assembler is case-sensitive: instructions must be written in lowercase (except for the R in immediate instructions).
  • E — 003: The instruction does not have a declared variable as its second argument. This argument must not be a number.
  • E — 004: The instruction is missing some required parameters.
    Refer to the previous chapter to check how many and which arguments are needed for each instruction.
  • E — 005: This instruction requires a general-purpose register (AX or BX) as the first argument.
    The parameter written in the first position is likely incorrect or undefined.
  • E — 006: The instruction has more parameters than required.
    Refer to the previous chapter to check how many and which arguments are needed for each instruction.
  • E — 007: The argument associated with this direct instruction is too large!
    Remember that acceptable values range from 0 to 255.
  • E — 008: The argument associated with the direct instruction is not an integer.
    It is likely that an alphanumeric value was entered or that this parameter has not been defined.
  • E — 009: The variable has not been declared at the beginning of the program.
    Ensure that the variable name is spelled correctly and that the name written in the declaration matches the one used in the instruction.
    Remember that the assembler is case-sensitive, and the declaration must follow the structure:
    [variable name] = [numeric value]
  • E — 010: The value associated with the variable is not an integer.
    An alphanumeric value may have been entered, or this parameter has not been defined.
  • E — 011: This variable has been defined multiple times within the code.
    Remember that to change the value of a memory cell, you must use processor instructions and not assignment operations; the latter are only for declarations.
  • E — 012: The value associated with this variable is too large! Acceptable values range from 0 to 255.
  • E — 013: The label has been called but not declared.
    Each label must be defined with the syntax [label:].
    Check for any spelling errors or issues related to the case-sensitivity of the assembler.
  • E — 014: This label is defined multiple times within the code.
    A label can have only one definition, and two labels cannot share the same name.
  • E — 015: You have declared too many input devices.
    Flip01 supports up to 32 different input devices.
  • E — 016: You have declared too many output devices.
    Flip01 supports up to 32 different output devices.
  • E — 017: The name assigned to this variable is a number. Variable names must contain at least one letter.
  • E — 018: You have declared a device as input but then used it as output.
    Ensure that the declaration and usage are consistent (> input).
  • E — 019: You have declared a device as output but then used it as input.
    Ensure that the declaration and usage are consistent (< output).
  • E — 020: You are using a device that you have not declared.
    Ensure that the name matches the declaration and remember that Flip01 is case-sensitive.
  • E — 021: The name assigned to this label is a number.
    Label names must contain at least one letter.
  • E — 022: The name assigned to this device is a number.
    I/O device names must contain at least one letter.
  • E — 023: You have not specified which device to use for this input or output operation.
  • E — 024: The name you have chosen for this I/O device has already been used for another device.
  • E — 025: The port number you have chosen for this device has already been assigned to another device.
    To avoid conflicts, use different ports for different devices.
  • E — 026: The field that should contain the port number for the I/O device does not contain an integer.
    Please check the format.
  • E — 027: The port number assigned to this device is not a valid integer.
    Remember that port numbers must be between 0 and 31.

Caution

The assembler is only compatible with Windows-based operating systems

MICRO PROGRAM COUNTER

The micro program counter is a section of Flip01 that, upon receiving the op-code of the currently active function, sequentially produces all the control signals that regulate the state of each component of the circuit and define the operation of the micro-instructions associated with that instruction.

To achieve this, the following steps are executed in sequence:

  1. The micro program counter reads the first micro-instruction from a third memory (MEM3) at the address corresponding to the op-code of the instruction.
  2. If there is another micro-instruction linked to that instruction, it executes it; otherwise, the address returns to 0, executing the fetch instruction, which retrieves the next instruction to be executed. This process is repeated until the described condition becomes true.

9

The mechanism that outlines the sequential operation of Flip01’s micro program counter.

The memory accessed by the micro program counter (MEM3) is non-volatile, and the set of control signal instructions is referred to as the ISA (Instruction Set Architecture).

Tip

One last note before wrapping up : if you enjoy these kinds of projects, feel free to follow us FOR FREE on Patreon! You’ll get early updates and exclusive content, and even a “simple” follow really helps keep these projects free and open source. Thanks :)

How to Use the Processor (Beginner’s Tutorial)

The first step is to open the file Flip01_Circuit.circ with Logisim Evolution.
At this point, the file containing the entire circuit should open, but for now, we'll set it aside since we first need to generate the code for the CPU to interpret.

To do this, you will need to open the assembler and write the code, or open a previously prepared file for this purpose (you can find examples in the Code Examples folder).
If the code is correct and contains no errors, the assembler will directly produce the code readable by the CPU.

It should look like this:
5

Program successfully assembled: No errors or warnings detected.

At this point, simply copy the first line of hexadecimal values (the one corresponding to the data) and paste it into the RAM MEM1 component in the Logisim file. To do this, right-click on the component and select Edit Contents.

You will need to repeat the same process with the second line of hexadecimal values (the one corresponding to the instructions), pasting it into the RAM MEM2 component in the Logisim file. Again, right-click on the component and select Edit Contents.

Now everything is ready to start the simulation. In the main menu at the top, click on Simulate to open a dropdown menu.
You can modify the "simulation speed" by changing the clock frequency in the Auto-Tick Frequency section (a higher value corresponds to a greater execution speed).

Once you have made this choice, in the same dropdown menu, just press Auto-Tick Enabled, or use the shortcut Ctrl + Kto start the simulation.

As mentioned at the beginning of the guide, all the referenced files and additional materials are available here on GitHub and on Patreon for free.

Flip01 has been in development for a long time, perhaps too long for what it is, and yet it is still far from being a complete project.
There are still many possible implementations to explore, which is why I’ve made it open source.
Now Flip01 is in your hands; use your creativity, play with it, break it, and push beyond every limit we have designed.
Have fun! :)

Flip01 is a project by Biasolo Riccardo and Croci Lorenzo, developed for the Pescetti Studio collective.

If you come across any errors, inaccuracies, or typos, feel free to reach out to us!
Send us an email at [email protected] with [bug] at the beginning of the subject line.

Updates

FliPGA01

Flipga01v2 Flip01 now also has an FPGA implementation!
The project, called FlipGA01, is of course free and open source.
You can find all the files here on GitHub, and there’s a detailed guide available on Medium.

It’s Dangerous to Go Alone! Take This

IDTGATT This one’s all about the assembler, and it introduces three handy tools to make your life a little easier:

  1. Conversion Table
    Now you can quickly switch between binary, decimal, and hexadecimal values using a dedicated button.
    The converter opens in a separate window, leaving the main assembler workspace untouched.

  2. View Manual
    A new button lets you open the free Flip01 manual in your default browser.
    This free detailed guide covers not just how to program Flip01, but everything you need to know about the processor itself.

  3. Auto-Save Feature
    This one's a bit behind-the-scenes: every change you make in the assembler is now automatically saved to a file named log.txt in the same folder as the assembler.
    If the program crashes unexpectedly, you won’t lose your work!
    Simply click Open File after restarting, select log.txt, and pick up right where you left off.

Instruction Bonanza

coming soon - December 16 2024, already available for free on Patreon

X Factor

coming soon - December 23 2024, already available for free on Patreon

Do you like this stuff?
Support us with a donation on PayPal!
It helps us keep everything free and open source for everyone.