Close
0%
0%

ECM-16/TTL homebrew computer

16 bit Computer made from ttl logic chips

Public Chat
Similar projects worth following
The aim of this project is to build functional computer, based around 16 bit datapath, from scratch, by using logic chips of 74hc family. There are three parts of this project: hardware design, hardware build, writing software.
For design, the "Digital" logic simulator (Logisim clone from H.Neeman, https://github.com/hneemann/Digital) is used. After part of the simulation is fully designed and works, and there are no further design change is planned, it is actual building/soldering begins. Throughout all this time software for computer is developed, and this development is not stopped after hardware completion. I plan to release instruction set and *.dig files publicly (when it becomes stable) , so anyone interested can write their own software for this computer, or tinker with it's design.

Computer design goals:

Architecture:

RISC-like – inspired by MIPS, but is quite different. This is Load/Store architecture, meaning that ALU operations are only applied on data in registers, and for using data from memory it should be first loaded to these registers, or stored from them back to memory, in separate instruction cycle.

The instruction set is presented in its own sub-project.

16-bit computer, 16-bit wide registers, 16-bit wide ALU and 16-bit bus.

Memory consists of 16-bit words.

Up to 4G bytes can be addressed in theory, by 32-bit addresses, byte-addressable memory. (Although most memory transfers would be word-sized). In real machine, no more than 16 MBytes will ever be installed.

Register transfer scheme:

Component base: 74HCxx SSI and MSI chips (Elementary logic, multiplexers, flip-flops, 8-bit registers, and counters).

Input-output (tentative):

Input: keyboard.

Output: Monitor (VGA): characters, pseudographics, bitmap.

Mass storage: 1GB CompactFlash card through Parallel ATA interface.

Registers:

Register file: 8 16-bit registers, 3-address:

First address (A operand) is written with result of ALU operation on 2 registers (B and C operands). ;

Some ALU ops use only 1 register, as accumulator;

Memory Pointers: 8 16-bit registers paired to yield Program Counter, Stack Pointer, Frame Pointer and Base Pointer (all 32-bit);

Instruction register: 16-bit, holds running instruction;

Memory Data Buffer: 16-bit, holds additional data for some instructions;

Memory Address Buffer: 32-bit, holds result of Address Arithmetic Unit;

Interrupt Vector: 16-bit, holds address of Interrupt Service routine start;

Status Register: 4-bit, holds Main ALU output flags.

ALU (16-bit):

Functions: ADD, SUB, AND, NOR, XOR, SHIFT, ROTATE

B operand modifications: no, invert (1-complement), twos complement, replace with: 0, 1-255.

ALU Adder: fast adder (16-bit, with carry look-ahead) for high speed.

Addressing:

Several different addressing are to be supported: Immediate, Direct, Indexed etc.

Address Adder ( 32-bit, with carry look-ahead ), included into Address Arithmetic unit, which adds signed offset to Memory Pointer for indexed address calculation.

ALU together with register file are built, and assembled into the Calculating core. Its testing is described in more detail in its own project.

Computer050.zip

Added byte addressing mode; fixed couple of wiring bugs. Additionally, the simulation is pre-loaded with integer calculator program (decimal input and output), which does additions, subtractions, multiplications and divisions (also can calculate remainder of division). Numbers can be up to 9 digits long.

application/x-zip-compressed - 1.67 MB - 01/13/2020 at 06:37

Download

Computer042.zip

Several bugs fixed, addressing logic remade from scratch, so it more regular, plus adding true subroutine calls. Comes pre-loaded with 16-bit positive integer multiplication program, input and output are hexadecimal.

x-zip-compressed - 2.29 MB - 12/12/2019 at 05:44

Download

asm3.zip

Third version of assembler; supports constant and address labels, several directives.

x-zip-compressed - 1.46 MB - 12/12/2019 at 05:42

Download

asm2.zip

Second version of assembler: couple of bugs fixed, now can have arbitrary number of spaces in lines, and, most important, address labels are supported.

x-zip-compressed - 1.43 MB - 11/30/2019 at 08:20

Download

asm.zip

simple assembler for code for use with simulated computer

application/x-zip-compressed - 1.43 MB - 11/24/2019 at 13:24

Download

View all 8 files

  • New sub-project describing the instruction set

    Pavel01/26/2022 at 10:39 0 comments

    I created a new project related to this one for publishing the instruction set used by this ECM-16/TTL cpu. In that project all of the instructions are going to be presented and fully described in one place.

  • State Machine and instruction types

    Pavel12/30/2021 at 18:03 0 comments

    When I was coming up with new Register Transfer Scheme,  and in general while rethinking the design approach for this CPU, I realised that the explicit state machine will be the way to go. At the time I thought it would consist of quite a few of states. But while devising the ways words need to be shuffled around the CPU (to enumerate needed states) for every instruction, I found out that there is a lot of commonalities between some of the states, and they can be grouped so that difference between them would be one or two signal lines. It occurred to me that there is a way to make the number of states low, but have the variations inside states that would be disambiguated via combinatorial circuits. Example would be Load/Store state, they have a common pattern for addressing logic, but different for the data moving logic. Yet, this difference can be wholly controlled by just one bit in instruction, which makes trivial the disambiguation.
    Thus I weeded the total number of states to 3 fetch states and 10 execution states.

    Here is a diagram which shows these states and transitions between them:

    As can be visible from the diagram, there are 8 instruction types:

    1: ALU operations -- all are executed in 1 step and decoded inside Calculating Core

    2: Loads/Stores via MP +offset -- set of 8 instruction pairs (load/Store) of variable length of execution

    3: Loads/Stores via direct address

    4: MOV -- copying of data between registers

    5: LDi -- loading immediate value into register

    6: Jumps -- set of 16 conditional and 2 unconditional instructions updating Program Counter, use the same states as (2)

    7: Address Arithmetic -- arithmetic operations on 32-bit values in MP (adding 8- and 16-bit signed values to MP)

    8: miscellaneous -- different instructions like NOP, HLT, SetIM, multi-word prefix etc.

    These instructions comprise the full set intended for my CPU.

  • Dealing with Jumps and Interrupts

    Pavel12/29/2021 at 15:40 0 comments

    During more rigorous contemplation of interrupt mechanisms, I found out that there needs some adjustments to be done to the Register Transfer Scheme.

    Here is the updated one:

    The new things added are 32-bit Interrupt Vector register, and a couple of associated selectors.

    I think I finally got it about how to implement interrupts. It was quite perplexing, and I thought it should have to be some complex sequence, and I also regarded it as not a pressing issue and haven't given them much thought. Now, really thinking about interrupts, I came up with seemingly good solution -- just use the sequence for Jump to Subroutine, just with some tweaks. Below is more in-depth discussion:

    First, the Jump sequences:

    Regular Jumps are 2-word loads to PC via MP (+ offset).

    Jump instruction sequence
    0    Fetch1        // load IR <-- Mem:PC;       PC <-- PC+2
    0'    (Fetch2)    // load MDB <-- Mem:PC;   PC <-- PC+2
    1    Set 2w flag
    2    Load MAB <-- MP+offset  // offset may be GPR or MDB
    3    Load PCH <-- Mem:MAB; load MAB <-- MAB+2
    3*   Load PCL <-- Mem:MAB; load MAB <-- MAB+2

    The regular Jumps are Unconditional Jump, and 8 jumps on different conditions:

    jump on Carry, Overflow, Negative, Zero, Not Carry, Not Overflow, Not Negative, and Not Zero.

    The Jump to Subroutine is in Jump family, but it has additional steps in sequence:

    it is a 2-word store of PC via MP -4 (stack pointer), followed by 2-word load to PC via PC+offset.

    Offset source, code and Stack pointer are the same as corresponding parts of regular memory transfer via MP instructions

    JSR instr sequence
    0    Fetch1       // load IR <-- Mem:PC;      PC <-- PC+2
    0'   (Fetch2)    // load MDB <-- Mem:PC;  PC <-- PC+2
    1    Set 2w flag
    2    Load MAB <-- MP-4 // most common MP will be SP
    3    Store Mem:MAB <-- PCH;  MAB <-- MAB+2
    3*   Store Mem:MAB <-- PCL;   MAB <-- MAB+2
    4    Set 2w flag
    5    Load MAB <-- PC+offset // offset may be GPR or MDB
    6    Load PCH <-- Mem:MAB;   MAB <-- MAB+2
    6*   Load PCL <-- Mem:MAB;   MAB <-- MAB+2

    In this way the jump (Program Counter load) is preceded by storing the PC value to current stack tip, thus making it possible to return from the subroutine by loading stored PC value from Stack to PC.

    Enter Interrupt -- this instruction I thought would be something very special, and quite complex, but, as it looks like right now, turned out not that difficult. It was possible to implement it with special Fetch cycle, which updates the Instruction Register with hardcoded value of one type of JSR, without PC update, and a signal which substitutes address from Memory Pointer with address from Interrupt Vector register.
    It can be triggered explicitly, in code (software interrupt), or via hardware. In the last case the interrupt sequence is followed after the last step of currently executing instruction.

    The hardcoded JSR used for entering interrupt uses SP_h/SP_l pair of Memory Pointer registers forming the Stack Pointer. In this way, this single instruction enforces the convention of using this memory pointer as the default Stack Pointer.

  • Memory Pointers description

    Pavel12/24/2021 at 21:05 0 comments

    There are 8 16-bit registers paired to yield 4 effective 32-bit Memory Pointer Registers. Each of the 8 base registers can be accessed and written to independently. But they are used in pairs as source of memory address. In some cases they provide direct address, while in others, these memory pointers contain a base for address, while the address itself is calculated as this base plus some offset value, which can be provided from one of the General Purpose Registers, or from immediate value in memory right after the instruction word.

    Of these Memory Pointers, the first one, the Program Counter (PC) is special in a sense that it is implicitly used for fetch cycle at start of every instruction execution sequence as the source of address of that instruction, and has its value updated each time. It also is the implicit register for loading new instruction address in Jump instructions. Otherwise it can be accessed the same way as all three other MP registers.

    For these other 3 Memory Pointers, the distinction between Stack Pointer (SP), Frame Pointer (FP) and Base Pointer (BP) is only conventional, and they are really fully interchangeable.

    In the instruction set I am developing, there is no dedicated operations related to stack -- all the needed memory transfers for creating a stack structure are already present in a set of Load/Store operations.

    The list of main instructions/instruction types will be presented in coming days/weeks.

  • Contemplation of multi-word data transfers inside ECM-16/TTL computer

    Pavel12/23/2021 at 19:16 2 comments

    This is an expansion over addressing logic structure described previously.

    I think there is a need for providing a means to transfer blocks of data between registers and between registers and memory in a single instruction. This should increase execution speeds, especially where data-intensive calculations take place, tight cycles over arrays, and frequent procedure calls, where much of the data needs to be transferred from registers to stack and back. It also will somewhat decrease program size.

    Execution speedup will be achieved with faster memory access operations: as the memory access operation takes several cycles to execute, mainly due to steps leading to address calculation, transferring several additional words will lengthen the operation execution by the number of cycles equal to the size of block in words. For example, if one-word transfer takes 4 cycles to execute, then two-word transfer will only take 5 cycles (instead of 8 cycles for 2 one-word transfers). Only penalty is 1-cycle prefix instruction for setting flag for number of words in transfer.

    Overall, execution cycles could be saved when using these multi-word data transfers. There are several types of memory access instructions depending on the address source, and they can take different number of cycles to execute in default mode (1-word accesses, can take 3, 4 or 5 cycles). But even in the least favourable case, when the access is relatively fast (only 3 cycles), and the need is only for 2 consecutive words at a time, this kind of multi-word transfer still makes sense as it saves 1 cycle (5 cycles for prefixed instruction vs 6 cycles for 2 consecutive 3-cycle instructions). For all other cases, savings rapidly increase with block length, and with instructions that intrinsically take more time to execute.

    As for the program size, prefix will add 1 word, so for 1-word memory access instructions, there will be no difference, code size wise, as to have single double-word access, or two single-word access instructions. For two-word instructions, and for block sizes of 4 words or more, the code size savings become apparent.

    Technicalities

    How to implement such transfers?

    In the Memory Address Unit, the output from MAB register is to be routed back to Address Adder via 2-to-1 mux (S8 on scheme above). By default (mux control: 0), Memory Pointer is provided to "Base" input of the adder. When multi-word transfer is executed, after transferring the first word, the mux control becomes 1, and MAB now is "Base" input, while "offset" is set to "+2".

    In the Control Unit there should be presettable counter which is set directly by prefix instruction. There also should be additional circuitry for overriding/modifying register address in instruction so as to transfer each word in the block to/from different register. For simplicity, some restrictions could be imposed: the lower bits of register address will need to be set to zero (or be ignored), so the instructions for double-word transfers could only have even reg addresses, ones for four-word transfers will address reg 0 and 4, and so on. Otherwise an additional small adder will be needed, and it will introduce some additional delay.

    The prefix would be a one-off, meaning any flags set by it will be cleared after execution of prefixed instruction. It would need to be inserted before each transfer instruction to make it multi-word, and also there will be no need for special clearing instruction afterwards, if single-word transfer is needed. Thus these flags are not to be saved, and will not appear inside Status register. 

    Flags:

    "2" -- transfer of two words to/from a pair of registers
    "4" -- transfer of four words to/from a half of Register File or half of MemPointer File
    "8" -- transfer of eight words to/from whole Register File or MemPointer File
    "16" -- transfer of 16 words to/from all registers at once

    The instruction loading all the registers at once will have an effect of almost total context switch,...

    Read more »

  • ECM-16/TTL Pilot-1 computer

    Pavel11/16/2021 at 09:06 0 comments

    UPD: I created a new project dedicated exclusively for this Pilot-1 cpu.

    This is fully functional, albeit severely limited, automated calculating machine.

    It can be viewed as having a kind of Harvard architecture, as it has all instructions in ROM, and data is in RAM (registers actually).

    The limitations are -- the program can be max 16 instructions long, and there are  only 8 registers where data can be operated on.

    As all the registers' data are visible on register display, it presents a good view in process of calculation -- all those lights blinking!

    This machine features full Main ALU together with Register File, completing the Calculating Core, with slapped-on Provisional Control Unit and HROM, that makes it whole.

    It is a good milestone towards full-fledged computer, that supposed to have full addressing and  control circuitry, as well as proper memory and I/O. As it is now, it gives some taste of what it will become some day.

    Here is block diagram of this contraption:

    And here is what it looked like when I first assembled it fully and tried to run it:

    There are several issues, mostly due to some unexpected behaviour of HROM board, and probably there are some bugs in the control board causing intermittent resets.

    The HROM had been lying around for a year, and it seems like it sustained some damage causing intermittent shorts on data line B. The glitches on the control board also haven't gone away.

    I hope, in the near future I'll weed out all these bugs, and system will work fully as intended. After this, next phase will be building memory access adder and registers, and combining them into single unit. In parallel, there will be finalisation of the instruction set and design of control logic.

  • Provisional Control Unit

    Pavel11/08/2021 at 09:46 0 comments

    This is a board that is providing sequencing for the Pilot-1 calculating machine:

    It may be divided into several function blocks:


    Instruction type decoder:

    It is recognizing 4 types of instructions: ALU, MOV, JMP and HLT:

    ALU instructions are for dealing with data modification in registers.

    MOV instructions are for moving data between registers.

    JMP instructions load address into 74hc163 counter inside the HROM board, thus effectively moving instruction pointer anywhere inside this 16-word instruction ROM; there are 3 kinds of these: unconditional jump, jump on carry, and jump on zero.

    HLT instruction is the one that turns off counting, and makes instruction pointer stuck on its address, making machine to be stuck looping just this instruction, halting any further execution. Can only be undone via Reset.

    Register:

    Carry and Zero flags are saved in 74hc74 dual D-flip-flop chip, to serve as Carry_in for ALU ops, and as flags for conditional jumps.

    Clock pulse generation:

    Multiple clock frequencies are provided: 1 manual, and 7 auto ( from roughly 10Hz up to 1MHz, which can be seamlessly switched between ). Two slowest frequencies (~10Hz and ~100Hz) are generated using 555 chips and can be smoothly adjusted via potentiometers, while higher 5 frequencies are derived from 1 MHz crystal oscillator and its signal progressively divided by 74hc163 synchronous counter.

    Buttons:

    Reset, clock pulse, switch frequency.

    LEDs:

    Indicating: 

    - main instruction type, and jump subtype;

    - C and Z flag value;

    - current clock frequency;

    - clock signal state;

    - Reset signal state.

    Preliminary testing results:
    For now I tested board all by itself, and it appears to work mostly as expected, but there are some issues:

    - I cannot access 5 highest clock speeds provided by crystal oscillator, somehow it switches all the way through right away after I release "change frequency" button (on falling edge), while it supposed to do the switching only when pressed on (rising edge).

    - probably related to, and most certainly causes the above problem is the presence of some spurious signals which cause the flipping of flag values in register when clock signal is at high level, and instruction is JUMP (the registers supposed to set/reset only during ALU instruction).

    These issues are probably due to coupling of high frequency signals coming from crystal and counter used to divide this signal frequency.

    Plans:

    In a couple of days I will test this board integrated with HROM and Calculating core as the Pilot-1 machine. I hope the above issues will not be blockers for working of the integrated whole, or will not be too difficult to solve. (Maybe just disconnecting power from crystal will be workable workaround, although in this case highest clock frequency bill only be 1.5 kHz).

  • Calculating Core complete!

    Pavel11/06/2021 at 15:43 0 comments

    Current state of affairs:

    The full stack for calculating core (ALU + Register File + Interface board) of this CPU is completed! And it is circa 40 cm high!

    After completing the interface board back in August, I've stacked this whole thing for the first time. When I started testing it, something was odd -- the thing was working erratically, some instructions were working seemingly ok, some others worked erroneously, yet others weren't working at all. At the time I disassembled and assembled the stack several times, but haven't been able to weed off all the bugs, and after a while I've got interested in some other things.

    Now, I've returned to this project, disassembled and took hard look at the boards comprising the Register File, took a good note of all the connectors, which signal gets to which wire, and found that in several places those wires were soldered to wrong inputs/outputs! Somehow this eluded me earlier. Anyway, after wiring bugs were sorted out, I've assembled it all, and now it seem to work ok. All the instruction types this Calculating Core is supposed to support are working ok, as far as I can tell.

    Now, this is a milestone passed, and I have a device with which I can do real calculations, provided right sequence of instructions. For now, these instructions can only be entered manually, one by one via a couple of switch banks.

    Plans for the next steps:

    In the next few weeks I plan to make a provisional control unit, which together with the Calculating Core and HROM will make the automatic calculation machine. It will not be a full-fledged computer, but a milestone towards it. I call it Pilot-1 for the lack of better name.

    Pilot-1 machine will have a 16-word ROM for its instructions, and will operate only on its 8 general purpose registers. 

    Supported instructions are all ALU instructions, the MOVs, and Jumps, conditional and unconditional. The latter ones are really needed to make maximum use out of only 16 instructions that can be inside the ROM.

    This Pilot-1 thing will not have any conventional I/O, aside from reset button and clock switch. Luckily, instructions are entered via switches on ROM board, and results are visible on display board, this should look like impressive light show. Blinky lights, yay! 


    Pictures:

    The Calculating Core board stack :

    The Register Display board in action:

    Current control interface:

  • Instruction translator board for ALU/RegFile

    Pavel08/16/2021 at 15:38 0 comments

    This board with a handful of chips serves as control interface between Calculating Core (ALU/RegFile assembly) and the rest of CPU. 

    It mostly does decoding of ALU instructions into ALU control signals, constant value, and register addresses. It also takes in ~R, clk, and a couple of control signals from Control Unit (yet to be developed) and distributes them to Calculating Core accordingly.

    Here is the board:

    And here are a couple views of testing process for the whole calculating core:

  • Display / interface board is complete

    Pavel08/06/2021 at 13:45 0 comments

    The registers content display, along with its control board, which also serves as control interface for the whole register file, is done.

    The display contains 160 LEDs, which show what is contained inside of each of 8 General Purpose Registers, along with indications of which of them is going to be loaded from bus, which is enabled to bus, and which one serves as Src1 or Src2 inputs for the main ALU.

    Color arrangement of the display:

    Display is made from 3mm thick white plastic, to the same dimensions as all the PCBs, so it can be stacked on top of other boards. I drilled holes in it so the LEDs are nicely fitted. Since I did it by hand, it shows in somewhat uneven spacing, but overall I am satisfied with its look.

    This part contains only 6 ICs, but there are a lot of connections:

    Here is the other side:

    And here the whole Register File stack, as seen from the side (there are some ribbon cables missing though):

    ---------------------------------------------------

    Next I want to make the Instruction Translation board for the ALU, and Provisional Decoder board: with these, and my 16-word ROM I will be able to integrate all the parts made to date into functional, albeit very limited, Harward-architecture type computer. It will have only 16 words for instructions, and only 8 words of volatile memory (actually, the Register File, just completed).

    This computer would be called "ECM-16/TTL Pilot", as this will be the first integrated test of the system, which will be capable to perform automated sequence of operations.

    Next, the addressing circuitry, which will be almost as complex as the whole Pilot, with proper Instruction Decode, Memory, Storage and Interface parts will be built, so the proper computer system is to be achieved.

View all 37 project logs

Enjoy this project?

Share

Discussions

Peabody1929 wrote 01/25/2021 at 19:15 point

Have you considered using a 74HC283 4 bit full adder with carry lookahead?  Or do you want to implement the adder at the gate level?  Using 4 bit or 8 bit wide parts would make the schematic simpler to read and the board easier to build.

  Are you sure? yes | no

Pavel wrote 01/26/2021 at 07:59 point

This chip is nice of course. But I tried to use the individual gates as much as possible. It also was interesting for me to develop such circuit by myself.

  Are you sure? yes | no

peter wrote 12/20/2019 at 17:39 point

Hi, great project!! how did you the transfer from Digital software to schematics and PCB design? Any tools, or made by hand?

  Are you sure? yes | no

Pavel wrote 12/26/2019 at 05:33 point

All by hand. Digital has library of DIL shapes for chips, so I start with replacing conventional logic element shapes with these, and then use it as reference when soldering the thing. I do not make or order custom PCBs, all is point-to-point soldering on perfboard.

  Are you sure? yes | no

Ken Yap wrote 11/24/2019 at 14:15 point

So no byte addressing? I suppose that keeps the addressing simple. Still, it means you will waste half the storage for characters and character strings.

This is the kind of CPU BCPL was targetting, but to handle strings, BCPL first had byte packing and unpacking routines, and later the % infix byte indirection operator.

  Are you sure? yes | no

Pavel wrote 11/24/2019 at 17:07 point

Well, I have consciously made such choice. And it doesn't mean that half of the storage is wasted, as character set could be made richer with more pseudographics and other non-Latin scripts (in my country the script used is Cyrillic, for example), and also maybe colour info encoded in the higher bits. On the other hand, basic Unicode is also 16-bit, so going this route, the strings can be made just that.

And I intend to use fairly modern and capacious storage, so having text info using up twice as much space than it would if I used byte-addressable memory is not that big of a deal.

  Are you sure? yes | no

Julian wrote 08/20/2018 at 23:31 point

Nice.  I think yours might be the only project on this site using Digital other than my 6-bit CPU (https://hackaday.io/project/159003-c61). :)

Out of interest, in case you need it I have a Digital plug-in library that provides a variable width/length FIFO component, which I've implemented for my planned IO processor project but haven't got around to actually using yet.  If you have any need for such a thing, let me know, and I'll upload it somewhere so you can use it.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

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