The Q CPU

A minimalistic 32-bit RISC V-ish CPU core.
The goal of the Q project is to create a completely independent CPU, toolchain, firmware and operating system.
What for? For fun, of course.
Is it useful for anything? Who knows.
Project repository: https://github.com/alape/qcpu
Outline
Data bus
CPU core has a bidirectional, 32-bit data bus. Bus input and output is split: bus_data_i, bus_data_o. CPU signals if whether it needs to read or write to the bus by toggling the bus_rw_o output.
There are no dedicated "chip select" outputs, bus device selection is done by virtue of splitting the address space at the side of bus crossbar, like so:
// some RAM @'h0 ~ 'h1FF
ram #(.A_MAX(512)) cpu_ram (
...,
.enable_i(bus_addr < 'h200)
);
// SIMIO IP @'h200 (enable_i is 1 << 9)
simio #(.A_WIDTH(9)) cpu_simio (
...,
.enable_i(bus_addr[9])
);
Registers
There are six special and ten general-purpose registers. All registers are 32 bit wide.
Special registers:
-
0x0 PC: Program Counter. Set to the bus address of the current instruction; -
0x1 SC: Stack Counter. Set to the memory address of stack's top. Has to be initialized by the user code (usually points to the beginning of.stackmemory section); -
0x2 SR: Status Register;-
SR[31:2]: reserved; -
SR[2]:IIrw, Inside Interrupt. Set to1whenever ISR is invoked and has not done running. Needs to be reset by ISR after it's done; -
SR[1]:IErw, Interrupts Enabled. Setting this to1enables inturrupts, setting this to0disables them. -
SR[0]:CFro, Carry Flag. Set bu ALU whenever algebraic operation produces a carry flag.
-
-
0x3 IR: Interrupt Reason. Whenever interrupt occurs, its code (current value ofirq_i) is loaded into this register; -
0x4 IV: Interrupt Vector. Contains the IRQ vector (same address space asPC). Has to be initialized by the user code.
General-purpose registers:
-
0x5 R0 -
0x6 R1 - ...
-
0xE R9
ISA
There are 35 opcodes, organized into 8 flavours (addressing modes).
See /isa for details.
Pipeline
CPU has six operation stages that are run in succession, somethimes they might get skipped if the current instruction doesn't require them:
-
FETCH_INSTR: bus address output is set to the current value of PC. This stage is never skipped. -
EVAL_OPCODE: current opcode is fetched from bus data input and decoded. This stage is never skipped. -
EVAL_ADDR: bus address output is set to the offset of instruction's operand. This stage is run only when opcode is eitherLDAorLDF. -
FETCH_OPERANDS: instruction's operands are fetched from instruction body or data bus input (depending on the flavour). This stage is skipped when opcode isN-flavoured. -
EXECUTE: instruction is executed. This stage is never skipped. -
WRITEBACK: instruction's result (if any) is output to the data bus, and the PC is incremented. This stage is never skipped.
At the moment, instructions are not run concurrently at different stages, so this state machine is not a true CPU pipeline in the strict sense.
Interrupts
Interrupts are input to the CPU core via irq_i bus. Interrupt code, i.e. format of irq_i, is not enforced by the CPU core and is between the user code and the system harness: this could be a true binary code, an unpacked BCD or else.
CPU core considers an interrupt to be active when value of irq_i changes to non-zero. When this happens (given that interrupts are enabled, SR.IE is set), CPU core does the following:
-
SR.IIflag is set; -
current value of
irq_iis loaded intoIRregister; -
current value of
PCis put on stack; -
value of
IVis loaded intoPC, so that the next instruction will be fetched from ISR.
When ISR completes its execution, it should reset the SR.II flag and RET to resume whatever routine was running at the moment interrupt fired.
How to use
Files
CPU core's logic is contained entirely within pipeline.v. It requires two header files: opcodes.vh and registers.vh.
Building a system
Instantiate the pipeline module from pipeline.v in your design and connect it to your IP modules (a basic example).
The qcpu repository contains few examples of IP modules to be used with the CPU core, such as basic RAM & ROM implementations, a GPIO module and simulated text I/O.
EasyMMap
IP modules that are used in systems running the Qsys LLR are expected to follow the EasyMMap address space guidelines.
First word of each module's address space is reserved for a EasyMMap ID — a unique IP identifier that facilitates the recognition of the module by LLR.
In most cases, EasyMMap ID are for bytes of ASCII text, made as human-readable as possible.
Known EasyMMap identifiers include:
-
GPIO(GPIO module) -
SMIO(SIMIO);
Test suite
Repository contains a few examples of FV testbenches, that confirm functionality of certain aspects of CPU core.
These testbenches employ a Verilog heder output feature of qas: each testbench's code snippet (code/*.s) is assembled to a corresponding Verilog header (ex. tc_000_sanity.vh), which is referenced in testbench's code to preload the contents of simulated ROM.
-
tc_000_sanity: general buildability of CPU core and basic aspects of operation (clocking, register access etc.); -
tc_001_gpio: GPIO module; -
tc_002_simio: SIMIO module; -
tc_003_irq: interrupts' functionality; -
tc_004_stack: operations with stack.
To post a comment you need to login first.