Josh’s Status Report 10/10

This week, I took the CPU from register-only programs to reading and writing memory, using a stack, and calling subroutines.

I gave the bus interface unit a second client, so data accesses share the bus with instruction fetch, with data going first when both request. I then added MOV loads and stores with BX-based addressing, a DS register and an address-generation unit. Each 16-bit access is two byte transfers on the 8088’s 8-bit bus, including words that cross a segment boundary or the top of memory.

Next I added the stack: an SS register, PUSH and POP, and near CALL and RET, which redirect instruction fetch like a taken jump. I checked PUSH SP and POP SP, where the 8088 differs from later x86 processors, against 10,000 recorded real-8088 cases each. A second write port for the stack pointer lets each instruction update everything on one clock edge.

Two repeatable demonstrations check registers, memory and every bus transaction under varied memory delays. The second runs two levels of subroutine calls and restores every register, including the stack pointer. Resetting the CPU in the middle of transfers exposed a bug where an interrupted load still wrote its register, which I fixed. The tests also caught all 16 deliberately broken versions of the design.

I also corrected our design report and added a CPU block diagram. Next are the full decoder, the remaining ALU operations, and interrupts, which the BIOS needs.

Josh’s Status Report (10/3)

This week, I took the CPU from a set of separate units to a working processor that fetches programs from memory and executes them, including branches and loops.

I started by defining the CPU side of the bus interface: single-byte transactions, where the CPU holds each request stable until memory signals completion after any number of wait cycles. I drafted these rules for review with Ben and built a simulated memory to stand in for his memory system.

On the CPU side, I implemented the bus interface unit, which captures instruction-fetch requests and returns each byte as memory completes it. In front of it I built a fetch controller with the 8088’s four-byte prefetch queue. It tracks its own fetch position and reserves queue space for requests already in flight, so the queue never overflows. I connected the fetch path to the execution unit in the CPU top level and added an architectural instruction pointer, kept separate from the fetch position, which runs ahead. The instruction pointer advances by each instruction’s length when it completes.

I then added control flow: the short unconditional jump and the conditional jumps JZ and JNZ, which test the zero flag. When a branch is taken, the execution unit sends the target address to the fetch controller, which redirects fetching there. It discards queued bytes and partially decoded instructions from the old path. Memory requests already on the bus can’t be cancelled, so it lets them finish and drops their bytes. A branch that isn’t taken simply continues to the next instruction.

With these pieces in place, the CPU runs a short counting loop loaded from an instruction-memory image at the 8088 reset address. It produces the correct final register values and instruction pointer, regardless of how slowly memory responds. One design observation is that the CPU is currently limited by instruction fetch rather than execution, since the bus delivers one byte every two cycles. That will matter when we tune timing.

I also updated our documentation for the team’s board choice, the Terasic DE10-Standard. Its microSD slot is wired to the ARM processor rather than the FPGA, which affects how we design storage access.

Josh’s Status Report (09/26)

This week, I began RTL implementation of our custom CPU in SystemVerilog, building and verifying the first vertical slice of the datapath. I implemented and tested the general-purpose register file, including byte-register access. I also built an ALU that performs 16-bit ADD and generates all six status flags (carry, parity, auxiliary carry, zero, sign and overflow). It passed over 20,000 directed and reference-model checks. Next, I implemented a decoder subset for MOV r16, imm16 and register-to-register ADD. It consumes instruction bytes through a valid/accept handshake and waits correctly when bytes are unavailable or when execution is not ready. I then built an execution unit that connects the decoder, register file, ALU and writeback through a small instruction controller. The controller retains each instruction’s operation and destination until it finishes. The unit correctly executes a short demonstration program (MOV AX,3; MOV BX,4; ADD AX,BX; ADD AX,AX, leaving AX = 14) and a longer flag-focused program, and it stops safely on unsupported instructions. To check that the tests are meaningful, I injected deliberate bugs into each module and confirmed the testbenches catch them. This also exposed a remaining gap in the execution-unit tests that I will fix. I set up a repeatable local simulation flow with Vivado’s XSim and Make targets for each module, and recorded the test results. I also drafted the CPU-side bus ports (single-byte transactions with explicit completion, plus the interrupt inputs) for review with Ben. The CPU cannot yet fetch instructions from memory, so only these two instruction forms run, fed directly by a testbench. Next week, I plan to review the bus interface draft with Ben. I then plan to build the instruction fetch path (instruction pointer, segment registers, prefetch queue and a minimal bus interface unit) with a simulated memory model, so the CPU can run programs from memory. I will also start extending the decoder and ALU to the full family of arithmetic and logic instructions.