Close
0%
0%

Small CPU in VHDL

Small CPU in VHDL

Similar projects worth following
IOP16B - I/O Processor with a deliberately minimal instruction set. Coded in VHDL. Useful for offloading polled I/O or replacing CPUs in small applications.

Video Series

The video series refactors the CPU from scratch and documents the details along the way.  Running on an inexpensive FPGA card.

IOP16B - I/O Processor with minimal instruction set

  • Useful for offloading polled I/O or replacing CPUs in small applications
  • Coded in VHDL
  • Runs at 50 MHz / 4 clocks = 12.5 MIPs
  • Small size in FPGA
    • Uses 226 logic cells in an Altera EP4CE15
    • Minimum 2 of 1K SRAM blocks (depends on program size)
    • Trade-off - SRAM could be replaced with logic cells (in theory)
  • 16-bit instruction size
    • Simple/consistent fields
    • 4-bit opcode
    • 4-bit register field (shared with address/offset)
    • 8-bit constant (shared with address/offset)
  • 12-bits of address / program memory
    • Allows for up to 4096 instructions in the program
  • Program is stored in FPGA ROM
    • Set size of program memory in INST_SRAM_SIZE_PASS generic
    • Up to 4KW of program size (12-bit address)
  • 8 registers in Register File
    • 8-bit registers (read/write)
    • Used for parameters/data (allocations below)
    • Reserved space in instruction for up to 16 of 8-bit registers
  • Peripheral bus
    • 8-bit address (controls up to 256 peripherals)
    • 8-bit data
    • Read strobe (a couple of clocks wide)
    • Write strobe (a couple of clocks wide)

Block Diagram

Opcodes

Capacity for 2 more instructions

  • ADI - x0 - Add immediate value to register
  • CMP - x1 - Compare register to immediate value
  • LRI - x2 - Load register with immediate value
  • Shift/Rotates - x3 - Shift/Rotate, Left/Right, Arithmetic/Logical
  • XRI - x4 - Exclusive OR register with immediate value
  • RSV1 - x5 - Unused, reserved for expansion
  • IOR - x6 - I/O Read into register
  • IOW - x7 - I/O Write from register
  • ARI - x8 - AND register with Immediate value and store back into register
  • ORI - x9 - OR register with Immediate value and store back into register
  • JSR - xA - Jump to subroutine (stack depth can be 1 or 16, set in STACK_DEPTH generic)
  • RTS - xB - Return from subroutine
  • BEZ - xC - Branch by offset if equal to zero
  • BNZ - xD - Branch by offset if not equal to zero
  • JMP - xE - Jump to address (12-bits)
  • RSV2 - xF - Unused, reserved for expansion

Fields

  • d15..d12 = opcode
  • d11..d0  = 12-bit offset (BEZ, BNZ)
  • d11..d0  = 12-bit address (JMP)
  • d7..d0   = 8-bit address (IOR, IOW)
  • d11..d8  = register number (LRI, IOR, IOW, ARI, ORI)
  • d7..d0   = Immediate value (LRI, ARI, ORI)

Stack

The stack determines whether subroutines are supported (JSR/RTS instructions). Sizes are:

  • 0 - No stack / no subroutine calls
  • 1 - Single level of subroutines
    • No nested subroutines
  • N>1 - 2^N deep
    • Supports deep nesting
    • Consumes 1 memory block

Registers

  • Reg0-Reg7 - Read/Write values
  • Reg8 - Hard coded to x00
  • Reg9 - Hard coded to x01
  • RegA-RegE - Not used (read returns x00)
  • RegF - Hard coded to xFF

Assembler

Assembler is written in Python (Python 3),

CSV file driven. Header is defined as:

['LABEL', 'OPCODE', 'REG_LABEL', 'OFFSET_ADDR', 'COMMENT']

Code examples are here

  • I2C Front Panel Control

    land-boards.com07/07/2021 at 20:22 0 comments

    There are several other IOP16 demos that use the Land Boards Front Panel I2C card. The Hackaday page for the Front Panel is here.

    The code controls the four MCP23016 parts. These are 16-bit I2C parallel I/O expanders. The advantage using the Land Boards Front panel card is it only uses 2 FPGA I/O pins to read 32 pushbuttons and write 32 LEDs. The IOP16 runs the Front Panel I2C interface. The GitHub for the Front Panel example is here.

    There are three examples so far.

    • FrontPanel01 - Controls the second port of a Dual Port SRAM simulating control of a VHDL Retro Computer
    • FrontPanel01B - Controls the second port of a Dual Port SRAM simulating control of a VHDL Retro Computer
    • FrontPanel01_Test_LoopBack - Loopback toggle of pushbutton to LEDs.

    There's also an example for Multicomp which controls an M6800 CPU running MIKBUG. This is a fully functional Front Panel example.

  • Blinkenlight Demo

    land-boards.com07/07/2021 at 20:03 0 comments

    New Timer Unit

    Added a timer unit to the IOP16. The timer unit allows a time value to be written to the Timer and the timer can be polled from the IOP16 to determine when the time has elapsed.

    The timer unit can count in uSecs (0-255), mSecs (0-255) or Seconds (0-255). Writing the appropriate time value to the timer unit starts the timer running. The timer status register indicates when the count is in progress (d0 = 1). This facilitates simple polling.

    Here's the IOP16 code to blink an LED off and every second:

    000    START    0x7800    IOW    #0x00    IO_00   WRITE TO LED    
    001             0x2001    LRI    Reg0     0X01    TIME 1 SEC    
    002             0x7006    IOW    Reg0     IO_06   STORE TO START TIMER    
    003    WAITDUN  0x6104    IOR    Reg1     IO_04   READ TIMER    
    004             0x8101    ARI    Reg1     0X01    CHECK BUSY    
    005             0xDFFE    BNZ    WAITDUN                    
    006             0x7900    IOW    #0x01    IO_00   WRITE TO LED    
    007             0x2001    LRI    Reg0     0X01    TIME 1 SEC    
    008             0x7006    IOW    Reg0     IO_06   STORE TO START TIMER    
    009    WAITD2   0x6104    IOR    Reg1     IO_04   READ TIMER    
    00a             0x8101    ARI    Reg1     0X01    CHECK BUSY    
    00b             0xDFFE    BNZ    WAITD2                    
    00c             0xE000    JMP    START                    
    

  • New GitHub repo

    land-boards.com07/06/2021 at 01:09 0 comments
  • Speeding Up the CPU

    land-boards.com07/03/2021 at 21:17 0 comments

    The IOP16 runs at 8 of 50 MHz clocks per instruction. That is 6.25 MIPS which really is way more than fast enough for this use. The states are Grey Coded as follows:

    Most of the cycles do nothing. There is no good reason this can't run twice as fast. The Grey Codes are:

    Made the change and it worked. It now runs at 12.5 MIPS. Way overkill, but why not.

  • Simple Application Example

    land-boards.com06/22/2021 at 21:42 0 comments

    Simple code example where the IOP16B reads the pushbutton and writes to the LED on the FPGA card. 

    Sources

    Memory Map

    Single peripheral address (0x00), data bit (D0).

    Top Level Entity

    entity TestIOP16B is
      port (
        -- Clock and reset
        i_clk      : in std_logic := '1';   -- Clock (50 MHz)
        i_n_reset  : in std_logic := '1';
        -- The key and LED on the FPGA card
        i_key1      : in std_logic := '1';  -- KEY1 on the FPGA card
        o_UsrLed    : out std_logic := '1'  -- USR LED on the FPGA card
      );
    end TestIOP16B;
    

    CPU Instance

    IOP16: ENTITY work.IOP16
    -- Need to pass down instruction RAM and stack sizes
      generic map     ( 
        INST_SRAM_SIZE_PASS  => 512,  -- Small code size since program is "simple"
        STACK_DEPTH_PASS     => 4     -- Single level subroutine (not nested)
      )
      PORT map (
        i_clk             => i_clk,
        i_resetN          => w_resetClean_n,
        -- Peripheral bus signals
        i_periphDataIn    => w_periphIn,
        o_periphWr        => w_periphWr,
        o_periphRd        => w_periphRd,
        o_periphDataOut   => w_periphOut,
        o_periphAdr       => w_periphAdr
      );
    

    Code

    List file is:

    000    SELF    0x6000    IOR    0x00    IO_00    read pushbutton
    001            0x7000    IOW    0x00    IO_00    write LED
    002            0xE000    JMP    SELF                    
    

     IOP_ROM

    The ROM file name has to be IOP_ROM and it gets stored in the current project folder. That allows for the IOP16B to be used in other projects. The Assembler creates a MIF file which gas to be loaded:

    IOP_ROM

    The IOP_ROM instance has two parameters: INST_SRAM_SIZE_PASS and STACK_DEPTH_PASS.

    -- Set stack size in STACK_DEPTH generic
    IOP16: ENTITY work.IOP16
    -- Need to pass down instruction RAM and stack sizes
      generic map {
        INST_SRAM_SIZE_PASS => 512,  -- Small code size since program is "simple"
        STACK_DEPTH_PASS    => 1     -- Single level subroutine (not nested)
    )
    

     INST_SRAM_SIZE_PASS has to match the size of the IOP_ROM.

    STACK_DEPTH_PASS can be 0, 1, or > 1 (typically 4 for 16) deep stack,

    Application Example Logic

    -- Peripheral bus read mux
    
    w_periphIn <= "0000000"&i_key1 when (w_periphAdr=x"00") else
                  x"00";
    
    -- Strobes/Selects
    w_wrLED <= '1' when ((w_periphAdr=x"00") and (w_periphWr = '1')) else '0';
    
    latchLED: PROCESS (i_clk)
    BEGIN
      IF rising_edge(i_clk) THEN
        if w_wrLED = '1' then
          o_UsrLed <= w_periphOut(0);
        END IF;
      END IF;
    END PROCESS;

View all 5 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates