CPU Architecture changes

2023/07/23

Tags: software

During my winter quarter last year I took a class on the RISC-V architecture and learned about the concept of pseudo-instructions, essentially assembler macros that expand to multiple machine code instructions that do one thing. I was inspired by this idea to implement it in my emulator and assembler since it would fix one of the main things I was unhappy with in the current design: the microsteps. The architecture previously had 32 microsteps which were completely unnecessary except for 2 instructions that used a few extra steps for stack operations. I decided to split up the PUSH R and CALL I instructions into two instructions each, and implement built-in macros for the assembler to make up for their disappearance.

Implementing assembler macros was not an easy task, and I ended up chipping away at it slowly while I worked through my finals, and a busy spring quarter. Currently, macros are stored as separate programs in a list that get injected by a preprocessing step after the lexing. This is kind of out of order compared to how they usually work, but it was easier to do this processing on tokens instead of characters. Currently the system works well enough to inject code containing labels, but I am still working on injecting immediate values and registers as operands.

With the macro expansion system working, I started rewriting the instruction set slightly to split the instructions that were too long. The CALL I instruction got split into an instruction I call PPC and a regular JP I instruction, and I modified the RET instruction a bit to skip the extra instruction upon return. PPC stands for Push Program Counter. PUSH R got split into PUT R, which pushes R to the top of the stack, but destroys the value in the process, and PEEK R which reads the top value of the stack without popping it. I think PPC and PEEK will both be useful instructions in their own right, so I think the decision to split the instructions was well worth it.

Modifying RET was not as easy as I thought though, since it was already at the 16 microstep limit and needed to increment the Program Counter one more time. I ended up moving around some instructions to make it so the invert part of sum out was tied to the last bit of the instruction, so odd instructions would only ever subtract, and even instructions would only ever add. I used this extra control line created by consolidating sum out and sum out subtract to create a new control line: sum out program counter enable. This behaves the same as the new sum out in that the invert part of the two’s complement circuitry is tied to the least significant bit of the instruction, but it also increases the program counter at the end of execution.

The new instruction table is as below:

0o00: NOP
0o01: PEEK <reg>
0o02: PPC
0o03: RET
0o04: PUT <reg>
0o05: POP <reg>
0o10: LD <reg>, <imm>
0o11: LD <reg>, <mem:abs>
0o12: LD <mem:abs>, <reg>
0o13: LD <reg>, <mem:rel>
0o14: LD <mem:rel>, <reg>
0o20: JP <imm>
0o21: JPZ <imm>
0o22: JNZ <imm>
0o23: JPC <imm>
0o24: JNC <imm>
0o25: JPN <imm>
0o26: JNN <imm>
0o30: ADD <reg>, <imm>
0o31: SUB <reg>, <imm>
0o32: ADDC <reg>, <imm>
0o33: CMP <reg>, <imm>
0o34: INC <reg>
0o35: ZRO <reg>
0o37: LOGIC <reg>, <imm>

Key: <reg> accesses a register, A or C
        <imm> is an immediate value
        <mem:abs> is an absolute memory address
        <mem:rel> is a relative memory address

The assembler and emulator for this architecture were both written in C and can be found here, and the most recent version is under 8 bit -> v0.2.

>> Home