As a hint to get you started, here are some possible instruction sequences for the first HLL statement:
Accumulator-based
load A
add B
store A
Stack-based
load A
load B
add
store A
Memory-to-memory (2-address)
add B, A
Register-based
load A, r1
load B, r2
add r2, r1
store r1, A
Your job is to figure out how big each instruction is, and also what the memory access patterns are for both instruction and data operations as each sequence executes.
For a real computer, you definitively would want more than 4 bits of program address since 4 bits only allows 16 instructions. So I came up with a scheme using a two-byte instruction for jumps, calls, load and stores which would give you a 12 bit address or 4096 location.
However, if you leave off this extra byte, then my instruction format allows for 5 bits (not just 4) of program address, and up to 4 bits of RAM addressing.
So the following is an instruction set based on the specification of two registers. All instructions are one byte except for the four requiring full addresses (optional, as described earlier, leave off this 2nd byte for 4 bit addressing).
I left in the long formats, because if one includes them, I think this would make a reasonable 8-bit computer (even though it can only address 4K bytes).
Although I favor memory-mapped I/O over input/output instructions, I provided two of each to satisfy the spec.
register-register instructions:
0 0 x x x x d s
where x x x x is the opcode,
d is the destination register 0 or 1,
and s is the source register 0 or 1
opcodes field:
0000 add d = d + s
0001 adc d = d + s + c
0010 sub d = d - s
0011 subb d = d - s - c
0100 and d = d and s
0101 or d = d or s
0110 xor d = d xor s
0111 not d = not s
1000 asr s = 0 arithmetic shift right d
(s=0 means s field is 0, not that the register is 0)
1000 asl s = 1 arithmetic shift left d
1001 ror s = 0 rotate right d
1001 rol s = 1 rotate left d
1010 inc s = 0 increment d
1010 dec s = 1 decrement d
1011 cmp d - s (no store)
1100 inp1 s = 0 input to reg d from input port 1
1100 inp2 s = 1 input to reg d from input port 2
1101 out1 s = 0 output from reg d to output port 1
1101 out2 s = 1 output from reg d to output port 2
1110 mul d/s = s * d (high byte of result into d, low byte into 1-d)
1111 sec ds = 00 set carry
1111 clc ds = 01 clear carry
1111 ret ds = 10 return from subroutine
1111 hlt ds = 11 halt
0 1 0 0 n n n n
brn - unconditional branch negative -n bytes (up to -16),
used for branching back at end of a short loop after a skip
instruction
0 1 0 1 b b i i
skip instructions, where
b b is type of branch
i i = # of bytes to skip typically 1 or 2, latter for
skipping over jump/call)
b b field:
00 scs skip i bytes if carry set
01 scc skip i bytes if carry clear
10 szs skip i bytes if zero bit set
11 szc skip i bytes if zero bit clear
0 1 1 r n n n n
load immediate to register r (0 or 1) signed value nnnn
+15 to -16
1 0 x p a a a a
a a a a a a a a (2nd byte only for extended format)
jump or call instruction (x = 0 is jump, 1 is call)
p is reserved for a page bit (or could just be the high
bit of address). 12 bits of address provide a direct call
or jump to 4K of program memory (or 5 bits provide
access to 32 bytes of memory).
1 1 x r i a a a
a a a a a a a a (2nd byte only for extended format)
load store from/to RAM (x = 0 is load, 1 is store)
11 bits of address provide direct access to 2K of RAM
(or 3 bits provides access to 8 bytes of RAM)
r is the destination or source register (0 or 1)
i field specifies indexed addressing using the register
not specified by the r field. if indexing feature left
off, then either 4K bytes or 16 bytes can be addressed.
There are three kinds of branches: jump and call instructions, which take a full address; an unconditional branch instruction that can branch backwards up to 16 bytes; and conditional skip instructions that can skip up to 4 bytes ahead. Using skips instead of branches allowed for a shorter address field. It could be redone as branches instead by getting rid of the load immediate instructions:
0 1 b b a a a a
conditional branch instructions, where
b b is type of branch
a a a a is signed relative branch +- 8 bytes
b b field:
00 scs branch i bytes if carry set
01 scc branch i bytes if carry clear
10 szs branch i bytes if zero bit set
11 szc branch i bytes if zero bit clear
The way the multiply works is as follows: an 8x8 multiply gives a 16 bit result. The multiply instruction always multiplies register 0 by register 1. The high byte of the result goes into register d, and the low byte goes into register 1-d. s is ignored.
I didn't implement the concept of multiplying the "input buffer" by the "data cache" since the OP didn't specify any details about the cache -- and I currently have the input buffer being read into either of the two registers. Loading the an input port into one of the registers, multiplying by the other producing a 16-bit product in both makes a lot more sense.
Except for the multiply, this could be implemented fairly easily; all of the arithmetic operations (add, subtract, compare) and logical operations (and, or, xor, not) can be performed by an ALU (Arithmetic/Logic Units), supported in Logisim. In real life, this might be implemented using two 4-bit 74LS181 ALUs cascaded together.
Best Answer
That instructable is kind of confusing. You'd be better off selecting an actual book from the big list.
Without getting into a discussion on various architectures which would just lead down the rabbit hole, I'll use the architecture described in the instructable and work through an example of a simple addition program.
Below is the RAM as described. On the left are the 16 addresses. Each address holds a byte. This byte may be data (demarcated as
D
) or an instruction consisting of an opcode (O
) and an address (A
).The program counter (PC) starts off at zero. This tells the processor to fetch the byte at address
0000
from the RAM and treat it as an instruction. So the processor fetches the byte into the Instruction Register (IR). The top four bits of the data retrieved go to the "control matrix" and the bottom four to the MAR. This split happens each time an instruction is fetched.Note: Those particular terms are not what I would consider typical (at least in my experience) but we'll go with them for this example.
The processor fetches the instruction at address
0000
since PC =0000
. Our first opcode is going to say, "move the data that is in address1000
into the accumulator" (I'm going to use prose instead of confusing things by picking a particular flavor of assembly language).So the processor fetches the data at address
1000
(let us say it is the number 2) and moves it into the accumulator (ACC). Now ACC = 2. The program counter gets automatically incremented so PC =0001
.The next instruction at address
0001
says, "add the data that is in the accumulator to the data at address1001
and store it back in the accumulator". So the processor takes what is in the accumulator and feeds it into one side of the Arithmetic Logic Unit (ALU). The processor takes the data that is at address1001
(let us say it is the number 3) and feeds it into the other half of the ALU. The ALU preforms the addition of the two numbers and the output (the number 5) is stored in the accumulator. Now ACC = 5. The program counter again gets automatically incremented so PC =0010
.The last instruction of our little program at address
0010
says, "store what is in the accumulator at address1010
". The processor then takes what is in the accumulator and stores it at address1010
. So now RAM address1010
= 5.Hopefully that example is a bit clearer picture of what is going on. Various architectures handles things slightly different ways. But the basic flow is usually similar.
Below is diagram of the basic registers and control circuits of most processors. There are a few more registers than we've been discussing. You can ignore those for the moment for the purposes of this discussion or read more about them at your leisure. Hopefully the visual aid will help make things a bit clearer.
Below is the flow of each step a processor takes. First it fetches an instruction and then that instruction tells it to fetch data to operate on from RAM.