This page is for a prior offering of CS 3330. It is not up-to-date.
In this lab we’ll write a program that correctly executes irmovq
, OPq
, rrmovq
, rmmovq
, and unconditional jmp
.
We suggest you start with your hcl2 homework solution that implements irmovq
, rrmovq
, and unconditional jmp
and then add one instruction at a time.
OPq
The textbook’s Figure 4.18 (page 387) notes the following semantics for OPq
:
Stage | OPq rA, rB |
---|---|
Fetch | icode:ifun ← M1[PC] rA:rB ← M1[PC + 1] valP ← PC + 2 |
Decode | valA ← R[rA] valB ← R[rB] |
Execute | valE ← valB OP valA Set CC |
Memory | |
Writeback | R[rB] ← valE |
PC Update | PC ← valP |
There are two things to do in the Execute phase.
The simple part is the operation itself, the ALU, which is essentially a mux based on ifun
with lines inside it like icode == OPQ && ifun == XORQ : reg_outputA ^ reg_outputB;
.
The other part is setting condition codes. We’ll use a simpler set than the textbook: instead of trying to track overflow and so on, we’ll simply check zero and negative directly.
We’ll need a register to store those three flags inside of:
register cC {
SF:1 = 0;
ZF:1 = 1;
}
(ZF
defaulting to 1 is consistent with yis
, but we won’t test what you choose as the initial value o fthe condition codes.)
Register banks like cC
have a special input stall_C
which, if 1
, causes the registers to ignore inputs and keep their current value. Thus, we want to stall C
unless there was an OPq:
stall_C = (icode != OPq);
Once we have done that, we record if the (signed) value of valE
is <, =, or > 0 (using unsigned comparison operators):
c_ZF = (valE == 0);
c_SF = (valE >= 0x8000000000000000);
If you run your simulator on y86/opq.yo, which is an assembled version of
irmovq $7, %rdx
irmovq $3, %rcx
addq %rcx, %rbx # b = 3
subq %rdx, %rcx # c = -3 11..1101
andq %rdx, %rbx # b = 2
xorq %rcx, %rdx # d = -6 11..11010
andq %rdx, %rsi
you should see (without the -q
flag, shown with some lines remove for brevity)
+------------------- between cycles 0 and 1 ----------------------+ | register cC(N) { SF=0 ZF=0 } |
+------------------- between cycles 1 and 2 ----------------------+ | RAX: 0 RCX: 0 RDX: 7 | | register cC(S) { SF=0 ZF=0 } |
+------------------- between cycles 2 and 3 ----------------------+ | RAX: 0 RCX: 3 RDX: 7 | | register cC(S) { SF=0 ZF=0 } |
+------------------- between cycles 3 and 4 ----------------------+ | RAX: 0 RCX: 3 RDX: 7 | | RBX: 3 RSP: 0 RBP: 0 | | register cC(N) { SF=0 ZF=0 } |
+------------------- between cycles 4 and 5 ----------------------+ | RAX: 0 RCX: fffffffffffffffc RDX: 7 | | RBX: 3 RSP: 0 RBP: 0 | | register cC(N) { SF=1 ZF=0 } |
+------------------- between cycles 5 and 6 ----------------------+ | RAX: 0 RCX: fffffffffffffffc RDX: 7 | | RBX: 3 RSP: 0 RBP: 0 | | register cC(N) { SF=0 ZF=0 } |
+------------------- between cycles 6 and 7 ----------------------+ | RAX: 0 RCX: fffffffffffffffc RDX: fffffffffffffffb | | RBX: 3 RSP: 0 RBP: 0 | | register cC(N) { SF=1 ZF=0 } |
+------------------- between cycles 7 and 8 ----------------------+ | RAX: 0 RCX: fffffffffffffffc RDX: fffffffffffffffb | | RBX: 3 RSP: 0 RBP: 0 | | register cC(N) { SF=0 ZF=1 } |
+----------------------- halted in state: ------------------------------+ | RAX: 0 RCX: fffffffffffffffc RDX: fffffffffffffffb | | RBX: 3 RSP: 0 RBP: 0 | | register cC(S) { SF=0 ZF=1 } |
You should also now be able to get the same results using your simulator as you get from tools/yis
when running y86/prog1.yo
through y86/prog4.yo
(and y86/prog8.yo
should still work too).
We have supplied traces of the output for all the .yo
files in testdata/seq-traces
.
cmovXX
The cmovXX
family of instructions have the same icode
as rrmovq
but non-zero ifun
s.
The simplest way to implement cmovXX
is to create a wire conditionsMet:1;
and set it using a mux with entries like ifun == LE : C_SF || C_ZF;
; then in the writeback stage of rrmovq
make the reg_dstE
(or reg_dstM
if that’s what you used) REG_NONE
if conditionsMet
is false.
Recall that muxes execute only the first true case, so adding something like !conditionsMet && icode == CMOVXX : REG_NONE;
before other cases when setting the dst_
should suffice. Remember that CMOVXX == RRMOVQ
, so that line would need to be before a line with an expression like icode == RRMOVQ
.
If you run your simulator on y86/cmovXX.yo, which is an assembled version of
irmovq $2766, %rbx # 0xace → b
irmovq $1, %rax # 1 → a
andq %rax, %rax # set flags based on a (>)
cmovg %rbx, %rcx # move if g (which it is): b → c (c now 0xace)
cmovne %rbx, %rdx # move if ne (which it is): b → d (d now 0xace)
irmovq $-1, %rax # -1 → a
andq %rax, %rax # set flags based on a (<)
cmovl %rbx, %rsp # move if l (which it is): b → sp (sp now 0xace)
cmovle %rbx, %rbp # move if le (which it is): b → bp (bp now 0xace)
xorq %rax, %rax # a ^= a means 0 → a, and sets flags (=)
cmove %rbx, %rsi # move if e (which it is): b → si (si now 0xace)
cmovge %rbx, %rdi # move if ge (which it is): b → di (di now 0xace)
irmovq $2989, %rbx # 0xbad → b
irmovq $1, %rax # 1 → a
andq %rax, %rax # set flags based on a (>)
cmovl %rbx, %rcx # move if l (which it is not): b → c (not moved)
cmove %rbx, %rdx # move if e (which it is not): b → d (not moved)
irmovq $-1, %rax # -1 → a
andq %rax, %rax # set flags based on a (<)
cmovge %rbx, %rsp # move if ge (which it is not): b → sp (not moved)
cmovg %rbx, %rbp # move if g (which it is not): b → bp (not moved)
xorq %rax, %rax # a ^= a means 0 → a, and sets flags (=)
cmovl %rbx, %rsi # move if l (which it is not): b → si (not moved)
cmovne %rbx, %rdi # move if ne (which it is not): b → di (not moved)
irmovq $0, %rbx # 0 → b
you should end with registers
| RAX: 0 RCX: ace RDX: ace |
| RBX: 0 RSP: ace RBP: ace |
| RSI: ace RDI: ace R8: 0 |
There is a trace of the expected cycle-by-cycle output in testdata/seq-traces/cmovXX.txt
.
rmmovq
The textbook’s Figure 4.19 (page 389) notes the following semantics for rmmovq
:
Stage | rmmovq rA, D(rB) |
---|---|
Fetch | icode:ifun ← M1[PC] rA:rB ← M1[PC + 1] valC ← M8[PC+2] valP ← PC + 10 |
Decode | valA ← R[rA] valB ← R[rB] |
Execute | valE ← valB + valC |
Memory | M8[valE] ← valA |
Writeback | |
PC Update | PC ← valP |
Memory is accessed by setting mem_addr
to the memory address in question and either
mem_readbit
to 0
, mem_writebit
to 1
, and mem_input
to the value to write to memory, which will cause the memory system to write a 8-byte value to memory; ormem_readbit
to 1
and mem_writebit
to 0
, which will cause the memory system to read a 8-byte value from memory into mem_output
.You will also need to compute the memory address as reg_outputB
+ valC
(the book suggests you do this in the ALU, meaning the same mux you used for OPq
’s adding and subtracting).
If both rmmovq
is implemented correctly, the test case y86/rmmovq-trivial.yo
should result in address 0xa2 containing byte 0x08. This test case is an assembled version of:
irmovq $2, %rax
irmovq $8, %rbx
rmmovq %rbx, 160(%rax)
You can run make test-seqlab
to test your processor over a variety of files. This will compare the output on the list of .yo
files in testdata/seqlab-tests.txt
to the reference outputs in testdata/seq-reference
.
Submit a file named seqlab.hcl
on the submission page.