16

I am reading and studying The Elements of Computing Systems but I am stuck at one point. Sample chapter skip the next 5 instruction s can be found here.

Anyway, I am trying to implement a Virtual Machine (or a byte code to assembly translator) but I am stuck at skip the next 5 instruction one point.

You can find the assembly notation here.

The goal is to implement a translator that will translate a specific byte code to this assembly code.

An example I have done successfully is for the byte code

push constant 5

which is translated to:

@5
D=A
@256
M=D

As I said, the assembly language for Hack is found in the link I provided but basically:

@5  // Load constant 5 to Register A
D=A // Assign the value in Reg A to Reg D
@256// Load constant 256 to Register A
M=D // Store the value found in Register D to Memory Location[A]

Well this was pretty straight forward. By definition memory location 256 is the top of the stack. So

push constant 5
push constant 98

will be translated to:

@5
D=A
@256
M=D
@98
D=A
@257
M=D

which is all fine..

I also want to give one more example:

push constant 5
push constant 98
add

is translated to:

@5
D=A
@256
M=D
@98
D=A
@257
M=D
@257  // Here starts the translation for 'add' // Load top of stack to A
D=M   // D = M[A] 
@256  // Load top of stack to A 
A=M   // A = M[A]
D=D+A
@256
M=D

I think it is pretty clear.

However I have no idea how I can translate the byte code

eq

to Assembly. Definition for eq is as follows:

Three of the commands (eq, gt, lt) return Boolean values. The VM represents true and false as -1 (minus one, 0xFFFF) and 0 (zero, 0x0000), respectively.

So I need to pop two values to registers A and D respectively, which is quite easy. But how am I supposed to create an Assembly code that will check against the values and push 1 if the result is true or 0 if the result is false?

The assembly code supported for Hack Computer is as follows:

enter image description here enter image description here enter image description here

I can do something like:

push constant 5
push constant 6
sub

which will hold the value 0 if 2 values pushed to the stack are equal or !0 if not but how does that help? I tried using D&A or D&M but that did not help much either..

I can also introduce a conditional jump but how am I supposed to know what instruction to jump to? Hack Assembly code does not have something like "skip the next 5 instructions" or etc..

[edit by Spektre] target platform summary as I see it

  • 16bit Von Neumann architecture (address is 15 bits with 16 bit Word access)
  • Data memory 32KW (Read/Write)
  • Instruction (Program) memory 32KW (Read only)
  • native 16 bit registers A,D
  • general purpose 16 bit registers R0-R15 mapped to Data memory at 0x0000 - 0x000F
  • these are most likely used also for: SP(R0),LCL(R1),ARG(R2),This(R3),That(R4)
  • Screen is mapped to Data memory at 0x4000-0x5FFF (512x256 B/W pixels 8KW)
  • Keyboard is mapped to Data memory at 0x6000 (ASCII code if last hit key?)

enter image description here

edgerunner
  • 14,873
  • 2
  • 57
  • 69
Koray Tugay
  • 22,894
  • 45
  • 188
  • 319
  • Actually it does seem like you have arbitrary jumps, check out to jmp instruction. You can use that to create two branches, one of which loads 1 into your destination register and one that loads 0 – Niklas B. May 10 '15 at 18:13
  • @NiklasB. Thank you for your response. But how will I know where to jump? The JMP instruction will jump to step which is loaded to Register A. What I mean is, how can I create Assembly Code that will say "jump 4 instructions" or "jump 8 instructions" ? – Koray Tugay May 11 '15 at 05:43
  • 1. Byte code to Assembly? ... Assembly source code is the text representation of the program (mnemonics) and that is compiled into Bytes ... usually code binaries. 2. comparing instructions are done on all architectures I came into contact with by substraction and setting the flags `Z,C` and some also include conditional branches (mostly MCU's) So the `cmp a,b` usually do `a-b` but throw the result away and leave just the flags. And now come in the condition (does not matter if in jump or whatever else instruction) `equal=(Z), greater=(NC),lower(C)` – Spektre May 11 '15 at 06:37
  • 3. On all architectures I know there are 2 types of jumps absolute(jumps to address) and relative(add signed offset to actual address) so use the second one they are widely ised for relocable programs because if you move them they will not change the compiled code in any way. I do not recognize your mnemonics but it looks to me that you are creating some microcode instead of assembly... what exactly are you doing? emulating or translating between two different assembly languages (different platforms) ? – Spektre May 11 '15 at 06:42
  • 4. you can use flags to set the return value ... if you have at your disposal things like `ADC,SBC` which is `+.-` with `Carry` and also you can usually shift `Zero` to `Carry` position to use this for equal too ...This way no jump is needed – Spektre May 11 '15 at 06:47
  • @Spektre This architecture is only for teaching purposes and it only has 2 registers. There are no flags to set or relative jumps. – Koray Tugay May 11 '15 at 09:36
  • @Spektre What exactly I am doing is something like this: Imagine you have a Hello World written in Java that is compiled to JVM Byte Code. This is the byte code I am talking about. And I have a given architecture. I need to interpret this byte code to Assembly of given architecture. – Koray Tugay May 11 '15 at 09:38
  • @Spektre And Assembly is not 1-1 translation usually. An assembler moslty will have a pre processor, isn't it? – Koray Tugay May 11 '15 at 09:38
  • so it is a cross compiler, also there must be more then 2 registers what about Stack pointer `SP` and Program counter `PC` ? state registers like flags ... , not just accumulator `a` and common purpose register `b` – Spektre May 11 '15 at 09:43
  • Interpreter/emulator/simulator simulates the target platform you have source/executable in one platform and converts it to another one then That is clearly cross compiler. Yes compiler needs preprocessor if you have things like labels, equ directives and more ... but for raw cross disassembly/compilation you do not need one – Spektre May 11 '15 at 09:46
  • @Spektre could you please see page 62 in here: http://www.nand2tetris.org/chapters/chapter%2004.pdf ? This is an architecture for teaching purposes only. There is no way to get the value of Program Counter. I mean there is no register provided to the student for PC. It is implicit. – Koray Tugay May 11 '15 at 09:55
  • if I see it right the `SP` is in RAM at address 0 and is 1 BYTE long so the stack is 256 BYTES and located god knows where (possibly 0xFE00...0xFFFF) so the push pop instruction of yours are not correct you need to read SP and use that as target stack location ... – Spektre May 11 '15 at 09:56
  • OK find the instruction set and also explanation of the SP,LCL,ARG,THIS,THAT mappings I assume SP is Stack pointer and THIS is PC but I am just guessing – Spektre May 11 '15 at 10:14
  • @Spektre Thanks for your time. So what you are saying is I should always push the stack pointer value to RAM as I start translating the virtual language? So if I am translating something like push constant 5, I should first set R0 to 256, then read it from there, and increment it? I edited the question to show that 'this' should be used for in the byte code of virtual machine running on top of the architecture. – Koray Tugay May 11 '15 at 11:23
  • @KorayTugay yes something like that ... `push value` is usually done like this: `dec SP; mov [SP],value;` and `pop value` like: `mov value,[SP]`; inc SP;` the stack can grow both directions (x86 is growing down, but there are processors that grow up). in the Hard coded push/pop implementation of yours you could not make something like `for (i=0;i – Spektre May 11 '15 at 11:52
  • @KorayTugay the memory access commands you added are not what I meant the `SP,ARG,...` are specific memory locations in Data memory and have a special meaning in your architecture ... try to find out what they mean – Spektre May 11 '15 at 11:56
  • btw your architecture seem to me like hybrid between MOS6502 and Atmel AT32UC3 processors – Spektre May 11 '15 at 12:09
  • I think in that case you have to know where each instruction is. I.e the assembler has to keep track of the offsets – Niklas B. May 11 '15 at 15:27
  • @NiklasB. The assembler is already provided and it does not have such feature. – Koray Tugay May 11 '15 at 16:05
  • Sorry, I meant the compiler that you write. It has to keep track of the offsets of the instructions it generates. – Niklas B. May 11 '15 at 16:47
  • @NiklasB. Yes, thanks. That is what I thought too, but it does not sound right.. There should be a better way I think.. – Koray Tugay May 12 '15 at 04:43
  • If you don't have a way to figure out the PC or to do a relative jump, there's probably no other way to do a full conditional branch. – Niklas B. May 12 '15 at 05:12
  • you could try to compute the output value branchlessly as an insane logical circuit bit by bit cascade and then code to your math functions but that will be insane. If your platform does not have relative jumps or absolute call/ret capability then you will not be able to translate any code to your platform! – Spektre May 12 '15 at 14:34
  • @Spektre Well there must be a way, this is an educational book. – Koray Tugay May 12 '15 at 15:45
  • We already told you a way, it's not our fault that you don't find it elegant enough. You should probably ask the author of the book in this case – Niklas B. May 12 '15 at 23:16
  • @NiklasB. I do not think it is anybodies fault.. – Koray Tugay May 13 '15 at 02:46
  • So your Hack instruction table suspiciously leaves out opcodes whose top 3 bits are 100, 101, 110. Are you sure you've told all the instructions available? – Ira Baxter May 13 '15 at 05:03
  • .... OK, the manual simply says nothing. Guess they are undefined. – Ira Baxter May 13 '15 at 05:13
  • @Spektre: I disagree (based on hair-splitting interpretation of the docs) that this machine has separate instruction and data memories. (If it does, it will be almost impossible code an indirect fetch, and that's totally brain dead). The so called GP registers aren't registers; they're just memory locations that the book happened to name. – Ira Baxter May 13 '15 at 08:13
  • 2
    *... how am I supposed to know what instruction to jump to?* Code a conditional branch to a symbolic label. There are examples in the manual,and I have coded such an example in my answer. (If you are generating binary code, you know where the branch instruction is; "skip 5 instructions" means "branch to binary location of branch, +5".) – Ira Baxter May 13 '15 at 08:28
  • @IraBaxter I got that conclusion from page 85 `5.2 The Hack Hardware Platform Specification` where it is directly specified from the start that the platform has 2 memory spaces RAM (Data memory) and ROM (Instruction memory) Also for Von Neumann architecture (see i8051 and clones) the GP registers and (sometimes even all the registers) are mapped into Data Memory space... So: 1.you can not do a self modifying code 2. nor access PC register directly hence it is screwy architecture unable to perform call instruction (ret,push,pop are possible) and that leads to not be able to code anything... – Spektre May 13 '15 at 09:57
  • @IraBaxter Just realized `anything` is not the right word it should be `everything` instead (in last commend end) sorry but My English is not very good .... and to top of it add the absence of ALU flags access which is insane – Spektre May 13 '15 at 10:06
  • @Spektre: Aha, another book section, I didn't notice that. OK, no self modifying code. And, so, no indirect addressing either in instruction set or by self-modifying code, no subroutine return addresses by any half-reasonable trick. (My answer was a valiant effort!). This machine will be incredibly hard to code anything for; so how on earth did the author produce his PONG program in Jack? Suggest OP go study it to see how it addresses these issues. – Ira Baxter May 13 '15 at 13:38
  • @IraBaxter as some chapters are not yet available implies in me that it is still work in progress and not all is working yet ... or not complete Instruction set is yet available which is really silly because that is the thing It should start with. Your Answer is not usable on this platform but still awesome please do not delete it – Spektre May 13 '15 at 14:09
  • @IraBaxter As this platform has all instructions with size of single 16bit word we can emulate PC counter (similary to SP emulation) by incrementing some memory location after each instruction (with step of 1+all the instructions needed for the safe increment) This way we can know where we are (if the memory location is set to starting address of the program) and hence can implement relative jumps and call emulation... I see no other way and this is insane ... unless encoded to some macro. this will cripple most of program memory with waste – Spektre May 13 '15 at 14:20
  • @Spektre: You don't need access to PC to "know where you are". You need the location of the "current" instruction, and the assembler can give that to you. My answer (broken as it is) takes advantage of that fact. That is just annoying. *Absence of indirect addressing is unforgivable.* – Ira Baxter May 13 '15 at 14:40
  • @Spektre The book is complete and sold in amazon.com for example.. – Koray Tugay May 13 '15 at 14:50
  • *On deeper investigation, it appears Hack does have indirect addressing.* See my 2nd answer. – Ira Baxter May 16 '15 at 11:25
  • 2
    The nand2Tetris website has a Q&A section. It seems that this question requires a very specific context, so that would probably be a good place to look. I believe that this is what you were looking for: http://nand2tetris-questions-and-answers-forum.32033.n3.nabble.com/Jumps-and-labels-when-you-wont-be-able-to-know-PC-line-number-td4028233.html – Noam May 16 '15 at 12:43
  • @Noam Thank you professor. – Koray Tugay Apr 12 '16 at 18:06

2 Answers2

11

It appears there is another chapter which more definitively defines the Hack CPU. It says:

The Hack CPU consists of the ALU specified in chapter 2 and three registers called data register (D), address register (A), and program counter (PC). D and A are general-purpose 16-bit registers that can be manipulated by arithmetic and logical instructions like A=D-1 , D=D|A , and so on, following the Hack machine language specified in chapter 4. While the D-register is used solely to store data values, the contents of the A-register can be interpreted in three different ways, depending on the instruction’s context: as a data value, as a RAM address, or as a ROM address

So apparently "M" accesses are to RAM locations controlled by A. There's the indirect addressing I was missing. Now everything clicks.

With that confusion cleared up, now we can handle OP's question (a lot more easily).

Let's start with implementing subroutine calls with the stack.

     ; subroutine calling sequence
     @returnaddress   ; sets the A register
     D=A
     @subroutine
     0 ; jmp
  returnaddress:

     ...

  subroutine: ; D contains return address
  ; all parameters must be passed in memory locations, e.g, R1-R15
  ; ***** subroutine entry code *****
     @STK
     AM=M+1         ; bump stack pointer; also set A to new SP value
     M=D            ; write the return address into the stack
  ; **** subroutine entry code end ***
     <do subroutine work using any or all registers>
  ; **** subroutine exit code ****
     @STK
     AM=M-1         ; move stack pointer back
     A=M            ; fetch entry from stack
     0; jmp         ; jmp to return address
  ; **** subroutine exit code end ****

The "push constant" instruction can easily be translated to store into a dynamic location in the stack:

     @<constant>  ; sets A register
     D=A         ; save the constant someplace safe
     @STK
     AM=M+1         ; bump stack pointer; also set A to new SP value
     M=D            ; write the constant into the stack

If we wanted to make a subroutine to push constants:

   pushR2: ; value to push in R2
     @R15           ; save return address in R15
     M=D            ; we can't really use the stack,...
     @R2            ; because we are pushing on it
     D=M
     @STK
     AM=M+1         ; bump stack pointer; also set A to new SP value
     M=D            ; write the return address into the stack
     @R15
     A=M
     0 ; jmp

And to call the "push constant" routine:

     @<constant>
     D=A
     @R2
     M=D
     @returnaddress   ; sets the A register
     D=A
     @pushR2
     0 ; jmp
  returnaddress:

To push a variable value X:

     @X
     D=M
     @R2
     M=D
     @returnaddress   ; sets the A register
     D=A
     @pushR2
     0 ; jmp
  returnaddress:

A subroutine to pop a value from the stack into the D register:

   popD:
     @R15           ; save return address in R15
     M=D            ; we can't really use the stack,...
     @STK
     AM=M-1         ; decrement stack pointer; also set A to new SP value
     D=M            ; fetch the popped value
     @R15
     A=M
     0 ; jmp

Now, to do the "EQ" computation that was OP's original request:

EQ: ; compare values on top of stack, return boolean in D
      @R15         ; save return address
      M=D
      @EQReturn1
      D=A
      @PopD
      0; jmp
@EQReturn1:
      @R2
      M=D        ; save first popped value
      @EQReturn2
      D=A
      @PopD
      0; jmp
@EQReturn2:
      ; here D has 2nd popped value, R2 has first
      @R2
      D=D-M
      @EQDone
      equal; jmp
      @AddressOfXFFFF
      D=M
EQDone: ; D contains 0 or FFFF here
      @R15
      A=M         ; fetch return address
      0; jmp

Putting it all together:

     @5           ; push constant 5
     D=A
     @R2
     M=D
     @returnaddress1
     D=A
     @pushR2
     0 ; jmp
  returnaddress1:

     @X                ; now push X
     D=M
     @R2
     M=D
     @returnaddress2 
     D=A
     @pushR2
     0 ; jmp
  returnaddress2:

     @returnaddress3   ; pop and compare the values
     D=A
     @EQ
     0 ; jmp
  returnaddress3:

At this point, OP can generate code to push D onto the stack:

     @R2                ; push D onto stack
     M=D
     @returnaddress4 
     D=A
     @pushR2
     0 ; jmp
  returnaddress4:

or he can generate code to branch on the value of D:

     @jmptarget
     EQ ; jmp
Ira Baxter
  • 93,541
  • 22
  • 172
  • 341
  • This was my first opinion as well but I imagined I was missing something. Thanks for the great answer. But one more question: Where do I state there is no indirect addressing? – Koray Tugay May 18 '15 at 15:06
  • @KorayTugay: You didn't. You provided a copy of the documentation; I looked at the original, and that's all the data I had originally. Your only "sin" was pointing to a document that seemed to have the *only* information about the instruction set. Re-read the comments on your question to see how the thinking evolved. I sent a message to the book authors saying that splitting up the instruction set documentation was not a good thing to do. PS: I actually looked at Jack-generated code to convince myself I understood what it was doing. – Ira Baxter May 18 '15 at 15:43
  • Thanks for this. Although I ended up using a more primitive, subroutine-less `eq` implementation, I found your subroutine implementation very illuminating. While running this course for some reason it never crossed my mind to implement subroutines at an assembly level. – Rudi Kershaw Nov 07 '16 at 20:04
  • This would lead to jarringly slow execution. The code for calling a push subroutine is longer than just writing the original push, and a 5-line operation becomes an 18-line mess. Also, the stack pointer should always point to the next available slot by convention, so note that this method of pushing requires a paradigm shift in every other operation. – Matheus Leão Oct 18 '19 at 19:00
  • @MatheusLeão: People tame ugly instructions sets all the time by building interpreters, knowingly giving up performance to shorten implementation time. – Ira Baxter Jul 30 '20 at 14:00
  • @IraBaxter That's alright, but the Hack architecture has a very limited ROM capacity to begin with. The operating system by itself will probably not fit using this method. – Matheus Leão Oct 19 '20 at 18:53
1

As I wrote in last comment there is a branch less way so you need to compute the return value from operands directly

Lets take the easy operation like eq for now

  • if I get it right eq a,d is something like a=(a==d)
  • true is 0xFFFF and false is 0x0000
  • So this if a==d then a-d==0 this can be used directly

    1. compute a=a-d
    2. compute OR cascade of all bits of a

      • if the result is 0 return 0
      • if the result is 1 return 0xFFFF
      • this can be achieved by table or by 0-OR_Cascade(a)
    3. the OR cascade

      • I do not see any bit shift operations in your description
      • so you need to use a+a instead of a<<1
      • and if shift right is needed then you need to implement divide by 2

So when I summarize this eq a,d could look like this:

  • a=a-d;
  • a=(a|(a>>1)|(a>>2)|...|(a>>15))&1
  • a=0-a;
  • you just need to encode this into your assembly
  • as you do not have division or shift directly supported may be this may be better
  • a=a-d;
  • a=(a|(a<<1)|(a<<2)|...|(a<<15))&0x8000
  • a=0-(a>>15);

the lower and greater comparison are much more complicated

  • you need to compute the carry flag of the substraction
  • or use sign of the result (MSB of result)
  • if you limit the operands to 15 bit then it is just the 15th bit
  • for full 16 bit operands you need to compute the 16th bit of result
  • for that you need to know quite a bit of logic circuits and ALU summation principles
  • or divide the values to 8 bit pairs and do 2x8 bit substraction cascade
  • so a=a-d will became:
  • sub al,dl
  • sbc ah,dh
  • and the carry/sign is in the 8th bit of result which is accessible
Spektre
  • 49,595
  • 11
  • 110
  • 380