This is an RTL description of the DLX architecture as described in Computer Architecture, A Quantitative Approach, Second Edition, Hennessy & Patterson, Morgan Kaufman, 1996. This specific document uses the bit numbering found in Appendix C of the text; specifically, bit 31 is the high-order bit of the word. (This configuration is required in order to use the Arch library to simulate the DLX architecture.)
The DLX machine has 3 buses:
s1,
s2, and
dest.
Having two s buses allows us to have a separate bus for
each input arm of the ALU.
The dest bus is used to move output from the ALU.
A convention of the DLX is that the
s#
buses are used to get values from the registers, and the
dest
bus is used to put values on to the registers.
Thus, to move data from
reg[1] to reg[2] you
would perform the following steps:
s1 <- reg[1]
alu.out <- s1 (using OP1 passthrough operation of the ALU)
dest <- alu.out
reg[2] <- dest bus
(This isn't completely correct, but we'll get to that in a minute.)
The DLX has 32 general purpose registers in what is called a
register file, labeled reg[#].
In addition, it has the following special purpose registers:
PC: | the program counter | |
IR: | The instruction register | |
MAR: | the memory address register | |
MDR: | the memory data register | |
IAR: | interupt address register | |
TEMP: | a temporary register |
plus three registers used to make working with the register more orthogonal:
A: | a buffer between the register file and the s1 bus | |
B: | a buffer between the register file and the s2 bus | |
C: | a buffer between the dest bus and the register file |
The intent of these registers is to make all the ALU operations work
identically once the data from the source registers has been loaded into
A and B registers.
For example, to perform reg[3] <- reg[1] + reg[2]
you would perform the following steps:
A <- reg[1]
B <- reg[2]
s1 <- A
s2 <- B
add A to B
OP1 <- s1
OP2 <- s2
alu.out <- OP1 + OP2
dest <- alu.out
C <- dest
reg[3] <- C
Note that only steps 1, 2 and 7 use actual register numbers;
everything else only works with A,
B, and C.
Also note that this use of
A, B, and C
would affect the transfer described in the first section, so to do a
transfer from reg[1] to reg[2] you would
actually need to perform to following steps:
A <- reg[1]
s1 <- A
alu.out <- s1 (using OP1 passthrough operation of the alu)
dest <- alu.out
C <- dest bus
reg[2] <- C
The DLX uses 32-bit addressing.
The following operations are available to the alu:
s1 + s2 | add | |
s1 - s1 | subtraction | |
s1 & s2 | logical AND | |
s1 | s2 | logical OR | |
s1 ^ s2 | logical XOR | |
s1 << s2 | logically shift s1 left by s2 bit locations | |
s1 >> s2 | logically shift s1 right by s2 bit locations | |
s1 >> a | s2 arithmatic shift s1 right by s2 bit locations | |
s1 | pass through value of s1 | |
s2 | pass through value of s2 | |
0 | constant 0 | |
1 | constant 1 |
All instructions are 32 bits long. Bit 31 is the high-order bit of the word; for instructions, this is the beginning of the opcode. There are three instruction formats:
| opcode | 6 bits | |
| rs1 | 5 bits | |
| rd | 5 bits | |
| immediate | 16 bits |
rs1 is register, rd not used)
jump register, jump & link register (rd=0, rs
is destination, immediate=0)
| opcode | 6 bits | |
| rs1 | 5 bits | |
| rs2 | 5 bits | |
| rd | 5 bits | |
| func | 11 bits |
reg[rd] <- reg[rs1] func reg[rs2]
(func encodes the ALU operation, e.g. add, sub...)
also used for read/write special registers and moves
| opcode | 6 bits | |
| offset for PC | 26 bits |
[#]
and
[#,#]
to represent bit locations within a register, and
##
to indicate concatenation of values (e.g.,
"hi"##"_there"
gives you
"hi_there");
^
to represent duplication on a bit (e.g.
1^16
says to duplicate the bit
'1'
16 times giving the value
1111111111111111
or the hex value
FFFF).
MAR <- PC
IR <- mem[MAR]
PC <- PC + 4
A <- reg[rs1]
B <- reg[rs2]
Following the fetch, we will execute one of the following six instruction subgroups:
Note that floating point operations are not dealt with here.
if movi2s then IAR <- A
if movs2i then A <- IAR
Note that these RTL descriptions assume that memory can be read and
written in one-, two-, or four-byte units (e.g., LB reads
a single byte from memory into the low-order bits of MDR).
If memory can only be read and written in multiples of four bytes,
the load operations must select the appropriate byte from the resulting
contents of MDR, and the store operations must read the
relevant word from memory, replace the appropriate byte(s), and then
write the modified word back into memory.
MAR <- A + ((IR[15])^16 ## IR[15,0]
if load then
MDR <- mem[MAR]
if LB then
C <- (MDR[7])^24##MDR[7,0]
else if LBU then
C <- 0^24##MDR[7,0]
else if LH then
C <- (MDR[15])^16##MDR[15,0]
else if LHU then
C <- 0^16##MDR[15,0]
else if LW then
C <- MDR
else if LHI then
C <- (IR[15,0]<<16)##0^16
reg[rd] <- C
else if store then
MDR <- B
mem[MAR] <- MDR
if register-register
TEMP <- B
if register-immediate
if unsigned
TEMP <- 0^16##IR[15,0]
else
TEMP <- (IR[15])^16##IR[15,0]
All ALU operations will use the two source values
A and TEMP, so for all operations,
C <- A func TEMP
where func is defined by the operation being performed. A special note for shift right arithmetic:
if SRA then
C <- (A[31])^TEMP##(A>>TEMP)
Once register C has been set,
reg[rd] <- C
if register-register
TEMP <- B
if register-immediate
if unsigned
TEMP <- 0^16##IR[15,0]
else
TEMP <- (IR[15])^16##IR[15,0]
All set operations will compare the two values
A and TEMP, so to look at a single example,
if SNEQ then A != TEMP if yes then true so C <- 1 else false C <- 0
(Side note: code for the set instructions can be simplified by using subtraction to determine the relationship between the numbers; e.g.,
result = A - TEMP
if A = TEMP then
result is 0
if A > TEMP then
result is > 0
if A < TEMP then
result is < 0
and so on.) Again, once register C has been set,
reg[rd] <- C
if JR then
PC <- A
else if J then
PC <- PC + (IR[25])^6##IR[25,0]
else if JAL then
C <- PC
PC <- PC + (IR[25])^6##IR[25,0]
reg[31] <- C
else if JALR then
C <- PC
PC <- A
reg[31] <- C
else if TRAP then
IAR <- PC
PC <- (IR[25])^6##IR[25,0]
if BEQZ then
if A == 0 then
PC <- PC + (IR[15])^16##IR[15,0]
else
do nothing
else if BNEZ then
if A != 0 then
PC <- PC + (IR[15])^16##IR[15,0]
else
do nothing