08/21/2015 at 01:37 •
as I mentioned in my last update the computer is finished and operational. here's a picture of its finished state:
so what does finished mean?
-the computer is a physical representation of the architecture diagram
-all commands function
-it is capable of successfully running a multi-step, multi-instruction program
the first point is fairly straight forward as the architecture diagram omits a good deal of detail. all of the major parts of the computer seen on the architecture diagram have been present for quite some time.
the second point is more complicated. my last post described some of the difficulty I was having with the registers and the RAM. further inspection revealed another problem: the program counter would not reload to the correct value under any circumstances. these three problems meant that 7 of the 10 machine code commands did not do what they were supposed to. the first problem was that none of the registers would accept data from the ALU or ROM, however they would load correctly if given 'good' signals. I spent a lot of time trying to clean up the logic levels with discrete components but ultimately failed. because of this I was forced to use comparators. I don't feel particularly good about using integrated circuits but I'll discuss my justifications later. the second problem was the RAM load and output enable commands not working. this problem was much easier to fix. since both the load and output enable functions are handled on the arduino each of these lines is connected directly to a pin on the microcontroller. it turns out that the logic that was generating the load signals was not producing a high that the arduino recognized, to fix this i added an amplification stage between the discrete logic and the arduino. the final problem was similarly easy to fix. the reload line on the program counter can receive data from two sources, to produce a useful output the sources must be run through an OR gate, I had been lazy and built this OR gate out of diodes, I replaced the diodes with a simple transistor OR and some amplification and the function worked properly
the third point of my original list is the ability to run a non-trivial program. this was completed successfully on the 19th of August 2015, roughly two years after I began this project. the program I ran simply added 2 + 2 and stored the result in RAM address zero. 2 + 2 was calculated to equal 4. the program in its entirety:
mnemonic value command comment OE ROM NULL 0110 asserts ROM out bus to main bus LOAD B '2' 0010 0001 loads B register from main bus (since ROM is asserted to the main bus the value accompanying this command is also asserted to the main bus, therefore B is loaded with the value 0010 (2)) LOAD C '2' 0010 0010 same as above except that the C register is being loaded LOAD @ '0' 0000 0000 same as above except that the @ (adress) register is being loaded, RAM automatically points to the address indicated by the value stored in the @ register OE ROM NULL 0110 unasserts(?) ROM out bus from the main bus OE ALU NULL 0101 asserts ALU out bus to the main bus. since the ALU is combinational in nature the values stored in the B and C registers where added instantaneously LOAD RAM NULL 0100 RAM was loaded at the address indicated by the address register.
when the program was completed I switched the clock off and used the arduino ide to look at the contents of RAM. originally the above program produced the result 0010, however this appears to be due to the lsb and msb on the ALU bus being inverted (i put a plug in backwards) oops. after flipping the bus around the program produced the expected results.
so that about covers it. I made a computer. the following is my reflection on the project, feel free to skip to the tl;dr at the bottom.
was this project a success? throughout this project I have had two goals, ranked by importance they are:
1. learn how computers work
2. build a computer out of its constituent parts
I feel that the first goal was achieved. I started this project with no understanding of what a computer is. currently I am not entirely comfortable with the theoretical definition of a computer, however I feel that I have deepened my understanding of how these machines work and what constitutes a computer. in this regard the project was a success.
the second goal was not achieved. primarily because I did not understand exactly how complicated a computer is, and how little I know about electricity. the first casualty to complexity was the RAM, rather than build all of the RAM and accompanying access logic I opted for the significantly easier route of using a microcontroller. while this did feel like a defeat I believe this was a necessary step to success. without using the microcontroller this project could have become quite daunting and I may even have given up, which would have dealt a blow to my first goal. secondly I used integrated circuits to resolve issues I had with logic levels on two occasions. while I don't feel particularly good about this, I again feel it was necessary to achieve the first (more important) goal. I also feel that the use of comparators speaks to my very limited understanding of electricity and semiconductors. in this regard the project was a very near miss, I did not build a computer out of its constituent parts, but I very nearly built a processor out of discrete parts.
so, was this project a success? yes. I learned how computers work which was the primary point of this project. could it have been done better? absolutely.
next steps: in the immediate future I hope to clean up this project page and write a more complete details section. in the more distant future I hope to display this project at the upcoming Ottawa Maker Faire. I have applied to display it but have not received conformation.
as for my next endeavour in the world of computing, I hope to build another processor in the coming months. naturally I will document that project if it materializes. I would also consider building another computer out of discrete transistors, in an attempt to fix the mistakes I made along the way with this one (and make exciting new mistakes!).
I made a computer and I would do it again.
08/19/2015 at 21:54 •
I finished the computer today and successfully ran a multi-instruction program. details will be posted soon.
07/22/2015 at 17:03 •
I have successfully tested and rearranged all of the modules that make up the processor and on their own each module seems to be functioning. I have also built a new board:
this board is essentially just control logic it takes decoded instructions in on a 10-bit instruction bus and then turns them into usable load/output enable signals. its mostly just AND gates that AND the instruction and load clk to produce an appropriately timed load/out put enable bit. if you look at the new architecture diagram:
you'll notice that there are only three places data on the main bus can come from: RAM OUT, ALU OUT, and ROM OUT. to decide which location data is coming from the computer needs output enables, i.e. if you want to load the @ register with a value from ROM the ROM OUT bus must be enabled and the ALU OUT and RAM OUT buses must be disabled. to do this i built a 'bus toggle' circuit:
basically whenever the the above logic receives a 'toggle' command it switches to the opposite of what it was previously, i.e. data is being passed through>toggle>data is not being passed through or vice versa. this allows the programmer to decide where the data on the main bus is coming from by means of toggling the appropriate sources. a side effect of this is that a careless programmer can accidentally superimpose data from two sources onto the main bus at the same time. I actually only built two of the above circuit, one for ROM OUT and one for ALU OUT, the third I implemented in software on the arduino. while this is technically cheating, I don't feel it takes away from the project because, a) i have demonstrated that I can build the required circuit, and b) it is not technically part of the processor, as it is RAM access logic.
after building the control logic board I proceeded to assemble the computer and attempt to run the very first program, it failed.
as you would expect I was thoroughly disappointed that the computer did not work, I wasn't realy expecting to work on the first try, but still... any way I spent a lot of time retesting all of the modules in various configurations and discovered a few things:
1. each module works as expected if it receives 'good' signals (i.e. data one = v+ and data zero = v-)
2. the ALU works as expected
3. the program counter ROM and ROM access logic all work together as intended, including loops, if they receive 'good' signals
4. the register work if given 'good' signals
5. the RAM works if given 'good' signals
6. when all the pieces are assembled things stop working
this was pretty mystifying. each piece of the processor seems to work on its own but when I put them all together they seize up and stop working, the RAM refuses to load, the register store data incorrectly, and the program counter resets incorrectly. at present the best explanation I have for this behaviour is that data sources (ROM, ALU, (maybe RAM)) are not giving 'good' signals. the problem with this is that I don't really know what constitutes a 'good' signal. this is because I don't know what the logic levels are/should be for RTL. because of this I only know what 'perfect' signals are i.e. connect inputs directly to positive or directly to negative. when I measure the logic levels of the data on the main bus I can see that it is not perfect, (i.e HIGH != 5v and LOW != 0v) however I don't know how to fix this using transistors. if anyone has any ideas feel free to comment.
this problem is really bugging me because obviously some one figured out how to solve it because digital logic works, but how that was done I don't know. so far I've come up with two potential solutions for restoring logic levels to 'perfect' levels
1. relays. relays are switches so I could theoretically use them to switch rail voltages directly. the problem with relays is that they take lots of power to switch and I'm not sure if the fuzzy values coming out of my logic would be able to flip them
2. comparators. I've already used comparators on the project, but it was technically not in the processor, it was in the ROM access logic (cop-out, I know) I am fairly sure that using comparators would restore logic levels to working values but I am hesitant to use them because I don't fully understand how they work and as such would be forced to use a chip.
Ideally what I'd like to do is build a discrete circuit that is equivalent to an lm339, but I have no idea how complicated a thing to do that would be, and at this point I just really want to finish this project.
so in conclusion: I'm stuck because I don't know enough about electronics and the only way out seems to be the use of integrated circuits which kind of defeats the purpose of the project. if anyone has ideas (or a really good justification for using ICs) please comment.
06/23/2015 at 23:42 •
now that exams are over I've had some time to work on the computer. as mentioned in the last couple of posts the computer's architecture has changed quite drastically, because of this most of the I've been doing has been rewiring/reconnecting modules that are already built. when this is done and all of the modules are working satisfactorily I plan to start building a little more logic, so stay tuned.
04/23/2015 at 20:49 •
as I mentioned in my last project log I have changed the command set. I won't bother reproducing the old command set here, however this is the new command set:
mnemonic binary (decimal) description LOAD: following commands load the indicated unit with the value asserted to the main bus @ 0000 (0) B 0001 (1) C 0010 (2) PC 0011 (3) RAM 0100 (4) loads address pointed to by @ register TOGGLE BUS: following commands enable/disable the indicated unit's output bus ALU 0101 (5) ROM 0110 (6) RAM 0111 (7) asserts value located in address indicated by @ register MISC: miscellaneous commands TOGGLE ADD/SUB 1000 (8) toggles the ALU between addition and subtraction LOAD PC IF B==C 1001 (9) loads the PC with the value asserted to the main bus if the values in registers B and C are equal
as mentioned in previous posts the clock is split up into two cycles, a PC CLK and a LOAD CLK. a full clock cycle would go something like this:
rising edge of PC CLK:
new command/value is asserted to the ROM out bus
rising edge of LOAD CLK:
command is carried out
there are two major types of command, LOAD and TOGGLE BUS.
the load pin selected by the command is toggled high for the entire LOAD CLK
the write enable for the bus indicated by the command is toggled on the rising edge of LOAD CLK. if a write enablle was already high, it would be toggled low, if it was already low it would be toggled high.
if we wanted to write the out put of the ALU to RAM first the command TOGGLE BUS ALU would be executed, this command would would write the output of the ALU to the main bus. then the PC would increment loading the next command; LOAD RAM. this command would toggle the load pin on the RAM high for the entire LOAD CLK.
a side effect of this new command set architecture is that values from ROM will be available one clock cycle after the TOGGLE BUS ROM command. for example to get a value from ROM to RAM one would first have to execute a TOGGLE BUS ROM command opening the ROM BUS, the next instruction would contain the value to be loaded to RAM, as the value and instruction are contained in the same ROM memory address.
as you may have noticed the mechanical layout has changed, this is to cope with the increase in the number of boards required for the computer. all nine logic boards are mounted on a plywood frame that is about the size of a briefcase.
04/16/2015 at 02:41 •
last post I mentioned some problems. I seem to have them under control (read as: successfully swept under the rug) if you're interested in what was going on there's an explanation in the comments on the last post.
now for the big news.
for a while I have had a feeling that this thing was not designed as well as it could be. those of you familiar with processor layout probably noted some of the odd design choices that can be observed in the architecture diagram. notably the ALU is sort of smeared all over the place and there is not a main data bus. this leads to some weird behaviour such as an inability to load the @ register from RAM, another problem the architecture creates is a relatively ineffective conditional branching mechanism. for a while I figured I would just finish this project and build the next one right. no longer. I have decided to rework the computer's architecture. this will have a few advantages, first it will make the computer simpler, and second it will make the computer more universal. this move also comes with some setbacks, namely I'll have to take apart some of the stuff I've all ready built. fortunately all of the major logical blocks remain the same, most of the work should be in pulling them apart, not in actually rebuilding anything. all of the boards I've built will be used (except some multiplexers), but the will be connected in a new way.
here's the old architecture diagram:
and here's the new architecture diagram:
as you can see the new design is organized around a central bus removing the need for multiplexers everywhere. an indirect effect of the bus centered design is that the control logic is simpler. you'll notice that the first diagram says that the control logic is simplified, that was my way of saying I had no idea what I was doing (as opposed to now when I have almost no idea what I'm doing). the new diagram shows control logic that is basically one level above individual gates. the complexity of the control logic is the major reason for the redesign. for the old architecture to work I would have needed to design specific control logic for each command, now I need to design 3 sets of control logic and then implement it a few times. a side effect of the revised control logic is that there is also a revised command set, one that is hopefully more universal. later this week I will post a more in depth look at the commands, what they do ,and how they work, but this is it for now.
04/05/2015 at 02:10 •
in the last post I said I had fixed the timing problems. on closer observation I realized I had only made them slightly less noticeable. the idea of breaking up the clock is correct and the logic I proposed almost worked, but not in all test cases it turns out. I then spent some time designing slightly more well thought out logic and produced this:
the above logic would appear to do everything I want it to. when I connected it to the program counter everything went fine, but upon inspection of the output of the instruction decoder I realized that something is seriously wrong. the instruction decoder, which had appeared to be working now does not and I don't know why. as you may have noticed this project has been on the back burner for a while, mostly due to the timing problems, but I'm going to try and figure out what's going on with the instruction decoder. as of right now I'm pretty stuck. any advice would be appreciated.
02/22/2015 at 17:40 •
I changed the timing of the computer. rather than trying to execute an instruction on every clock cycle, I broke up the clk into two parts. a program counter clock and a load clock. diagram:
as you can see the program counter is incremented every other clock cycle. in stead of ANDing the clk and CMD (load @ instruction above) to produce the load @ signal I am now ANDing the load clk and CMD lines. another way of looking at it is that instead of trying to increment the program counter and write data in one clock cycle I am now incrementing the program counter in one clock cycle and writing data in the next clock cycle. the necessary logic is pretty trivial, basically a d flip flop and some gates, but the result is a working command! yay! also I'm pretty sure that since the above solution worked the double blip hypothesis was correct.
02/16/2015 at 18:25 •
I had always kind of assumed that it would be simplest to have the computer execute one instruction per clock cycle. it turns out this isn't the case. while my 1 instruction per clock, logic was correct I did not account for propagation delay.
here's a quick recap of how the computer's sequential logic fits together:
essentially the number coming out of the program counter is an address and is passed through the ROM access logic and selects and address (pink boxes). each address contains 8 bits of info (upper four are a value, lower four are a command). the value held in the ROM address indicated by the program counter is dumped onto the 8 bit bus. the upper half of the bus goes to the rest of the computer to do what ever the command tells it to. the lower half (the command) is sent through the instruction decoding logic, which is essentially a giant multiplexer (I.e 0110 (6 in decimal) goes in and 0000000001000000 comes out).
now if I wanted to load the upper half of the 8 bit ROM bus (V from now on), into the @ register I would need to pulse the load pin on the @ register. to do this I would program a value into the lower four bits of ROM (CMD from now on) that corresponded with the multiplexer output wire that was connected to the @ register load pin. I.e. if the 12th line coming out of the instruction decoding logic was connected to the @ register load pin I would set CMD equal to 1100. that way whenever CMD==12 the @ register is loaded with the value V.
there is a slight problem with the above explanation. V is asserted to the @ register input bus at the same time that CMD is fed into the instruction decoding logic, namely the rising edge of the clock. both values remain the same until the next rising clock, at which point they are replaced with the contents of the next ROM address. another thing to note: the @ register accepts a new value on the falling edge of the @ register load pulse. maybe you can see the problem. V and CMD change at exactly the same time. that means that the falling edge of CMD (the point at which the @ register loads) is the same point at which the value we are trying to load (V) changes, resulting iin garbage being loaded into the @ register.
solution? my first attempt at solving the problem was as follows. add an AND gate between the clock and the CMD lines and use the output to load the @ register. because the CMD and V values change on the rising edge of the clock but we need a falling edge to load the @ register we can use the falling edge of the clock to load the @ register. if the above paragraph made no sense fear not, here is the timing diagram:
load instr should say 'CMD'. basically Load @ = clk AND CMD. the above is logically correct, however you should note the word 'ideal' at the top of the page. as you may have heard propagation delay is a thing. I had assumed that this processor was small and slow enough that I wouldn't have to worry about propagation delay, not so! below is the 'real' timing diagram:
again, load instruction should be CMD. between the clock and CMD there are ~200 transitors. between clk and load @ register there are 2 transistors. there is a slight lag between the clk and CMD due to the number of transistors, compared to the AND gate between clk and CMD and load @. because of this the AND gate responds almost instantaneously and you get that little double blip on the @ register's load pin. this second blip also happens right as V is changing resulting in a garbage load of the @ register on the next clock. that stupid double blip took me two days to figure out.
I'm pretty sure that the double blip hypothesis is correct, it lines up with my observations quite nicely, however, I have not been able to observe this on my oscilloscope, although that's probably due to it being a pretty crappy oscilloscope and me not really understanding how to use it.
how does one solve a double blip? my best solution would be to split up the clock cycle. every other clock pulse would increment the program counter. the in between clock pulses would become a load clock that would be only be used to perform loads. I think I've got that circuit figured out, but I'll save that for another post.
if anyone has any ideas about how to catch the double blip with rudimentary tools, feel free to comment/message me. It would be nice to have direct confirmation before attempting to build some more logic.
***edit: I may have caught the double blip with my logic probe
01/31/2015 at 22:19 •
I successfully executed an instruction for the very first time. There are still some bugs to work out, but the first program shouldn't be very far off. the first instruction I executed was a load of the @ register. basically what I did was load the binary numbers 0-15 into the upper nybble of ROM, I then connected the output to the @ register input bus, and applied the clock signal. for this demo I connected the @ register load bit to the clock. eventually this will be connected to the control logic, but before that can happen I have some timing issues to fix in regards to the output of the control logic multiplexer. It shouldn't be long before this runs it's first program.