Search code examples
assemblylittle-man-computer

Output and Reset Lists in LMC


I am working at this coding challenge:

Write a program for the Little Man Computer that allows the user to manage a list of values. It should start with an empty list and then process input as follows:

If the input is:

  • less than 100: add this value to a list, unless the list already has 10 values, in which case the value is ignored
  • 995: make the list empty
  • 996: output the number of values the list currently has
  • 997: output each value the list currently has, in the order they were added to it
  • 998: output each value the list currently has, in reversed order
  • 999: end the program
  • Any other value is ignored

The processing of input values continues as long as the input value is not 999.

I'm having issues getting the code to print the stored list in forward order when 997 is input. I think I may have the ADD and SUB instructions confused. I also can't get the stored list to reset correctly when 995 is input.

Everything else I was able to program correctly.

Below is my code:

START   INP
        STA TEMP
        SUB NINES
        BRZ end

        LDA TEMP
        SUB EIGHT
        BRZ PRIT

        lda temp
        sub seven
        brz printf

        LDA TEMP
        SUB SIX
        BRZ DOOUT

        LDA TEMP
        SUB FIVE
        BRZ RESET

        LDA COUNT
        SUB TEN
        BRZ START

        LDA TEMP
        SUB HUND
        BRP START

SIT     LDA SINST
        ADD COUNT
        STA SLOC
        LDA TEMP
SLOC    DAT 0
        LDA COUNT
        ADD ONE
        STA COUNT
        BRA START
PRIT    LDA COUNT
        BRZ END
PRINTR  LDA LINST
        ADD COUNT
        SUB ONE
        STA LDIT
LDIT    DAT 0
        OUT
        LDA COUNT
        SUB ONE
        STA COUNT
        BRZ END
        BRA PRINTR

---PRINTF  LDA LINST
        ADD COUNT
        add ONE
        STA LDIT
LDIT    DAT 0
        OUT
        LDA COUNT
        SUB ONE
        STA COUNT
        BRZ END
        BRA PRINTF 

doout   lda count
        out
        bra start

reset   lda zero
        sta count
        bra start

END     HLT

TEMP    DAT 0
COUNT   DAT 0
ONE     DAT 1
TWO     DAT 2
TEN     DAT 10
HUND    DAT 100
SINST   DAT 380
LINST   DAT 580
five    dat 995
six     dat 996
seven   dat 997
eight   dat 998
NINES   DAT 999

Solution

  • The issues in your program can be categorised in:

    1. Issues related to labels that a good simulator should detect before running your program
    2. Issues related to program logic, that make the program produce the wrong output
    3. Issues related to code style

    1. Labels

    A good simulator should not accept the following errors:

    • PRINTF label is not defined.

      There is a brz printf instruction, but that label is not defined, since --- makes it a comment. Obviously that --- needs to be removed for the label reference to be valid.

    • LDIT label is duplicate:

      The label LDIT is defined twice. This will have unreliable behaviour. A good simulator should give an error message about this, but other simulators will just take one definition and ignore the duplicates. Either way, the intention of the program is that one STA LDIT uses the first LDIT location and the second STA LDIT uses the second LDIT location. If anything, this will not be what happens. So rename one of the two labels, and adjust one of the STA LDIT instructions accordingly.

    • zero label is undefined:

      The code to reset the counter refers to an undefined label zero. Again, good simulators will produce an error when loading the program, but others may silently use mailbox 0 instead, which leads to undesirable behaviour. So define the zero label as

      zero DAT 0
      

    2. Logic

    • The code for the printing of the list, either in forward or backward direction, both change the value of COUNT, but this way the code will loose what is the size of the list! For instance, if after such a traversal you would choose action 996 -- i.e. to query the size of the list -- it will not give the correct result. The solution is to use a different variable for traversing through the list, and leave COUNT untouched. COUNT should only be changed when you add a value to the list, or when you reset the list.

    • The code for printing ends by jumping to END, but it seems that the user should be allowed to continue with another "menu" option, so instead of jumping to END it should jump to START. The only reason to jump to END should be because the user inputs the choice 999.

    • For forward printing you should not start by adding COUNT to the dynamic load instruction, as you need to start with the first element of the list, not the last. So don't do ADD COUNT nor ADD ONE before printing the first time. Instead, increment the dynamic load instruction until its difference with the original load instruction -- i.e. calculate the relative offset in the list -- is at least COUNT.

    • The LMC specification has a particular oddity: it does not define what the accumulator's value will be when a subtraction would lead to a negative result. The accumulator is not able to store negative values, only flag a negative result. By consequence it is not safe to do BRZ when you have just performed a SUB instruction that could lead to a negative value (because strangely enough, a simulator might react with a zero value in the accumulator when a subtraction would be negative). In short, if you can, prefer using BRP instead of BRZ, or least use BRP before using BRZ. NB: even teachers on the subject are not always aware of this.

    • An LMC has a reset "handle", which sets the program counter back to the start of the program. When that happens you will want to start with a clean slate, and have COUNT reset to 0. So add the resetting code at the very top of your program.

    This will fix the semantic issues in your program.

    3. Code style

    • I would suggest that you use more meaningful names. doout is not very enlightening, as your program is designed to output different things (list in forward order, list in backward order, the size of the list). So for instance, use output_size instead of doout. Names like LDIT, PRIT, ... are quite cryptic. A program will be much easier to read and understand when it uses descriptive names, without abbreviations that only you will understand.

    • The dynamic instructions are based on SINST (store instruction) and LINST (load instruction), which are hardcoded as:

      SINST  DAT 380
      LINST  DAT 580
      

      But it is a pity that you have hardcoded them like that. First of all, they assume that mailbox 80 is free for storing the list, and secondly it requires knowledge of the opcodes of these instructions (3 and 5). Yet, with a good assembler you shouldn't have to do this. So I suggest to do this instead:

      store_instruction STA list
      load_instruction  LDA list
      

      ...and define list with a DAT instruction at the very bottom of your code (as that is where there are mailboxes available). I would even add dummy DAT lines after it, so to be very clear about the mailboxes that are part of that list -- it just makes it easier for someone to understand the code:

      list DAT
           DAT
           DAT
           DAT
           DAT
           DAT
           DAT
           DAT
           DAT
           DAT
      

      This way, the list may not be stored at mailbox 80, but we don't care. We leave it to the assembler to assign the next free mailbox for our list. It may also look strange to have STA and LDA instructions in the middle of your "data section", where they will never be executed, but the principle of the LMC architecture (Von Neumann architecture) is that code and data use the same memory, so this is fine. The LDA and STA instructions will be copied from there in to the actual program.

    Corrected program

    Taking all of the above into account, the program could look like this:

    #input:1 2 3 997 998 999
    clear_list        LDA zero # Start by resetting the list
                      STA list_size
    
    start             INP
                      STA input
                      SUB menu_nine
                      BRP end
    
                      LDA input
                      SUB menu_eight
                      BRP output_reversed
    
                      LDA input
                      SUB menu_seven
                      BRP output_forward
    
                      LDA input
                      SUB menu_six
                      BRP output_size
    
                      LDA input
                      SUB menu_five
                      BRP clear_list
    
                      LDA list_size
                      SUB max_list_size
                      BRP start
    
                      LDA input
                      SUB input_limit
                      BRP start
    
                      LDA store_instruction
                      ADD list_size
                      STA store
                      LDA input
    store             DAT 0
                      LDA list_size
                      ADD one
                      STA list_size
                      BRA start
    
    output_reversed   LDA load_instruction
                      ADD list_size
    loop_reversed     SUB one
                      STA load_reversed
                      SUB load_instruction # are we still within the list?
                      BRP load_reversed # yes, continue printing
                      BRA start
    load_reversed     DAT 0
                      OUT
                      LDA load_reversed # decrement the dynamic LDA instruction
                      BRA loop_reversed
    
    output_forward    LDA load_instruction
    loop_forward      STA load_forward
                      SUB load_instruction # get relative offset in the list
                      SUB list_size # are we still within the list?
                      BRP start # no, stop printing
    load_forward      DAT 0
                      OUT
                      LDA load_forward # increment the dynamic LDA instruction
                      ADD one
                      BRA loop_forward
    
    output_size       LDA list_size
                      OUT
                      BRA start
    
    end               HLT
    
    # constants
    zero              DAT 0
    one               DAT 1
    two               DAT 2
    max_list_size     DAT 10
    input_limit       DAT 100
    load_instruction  LDA list
    store_instruction STA list
    menu_five         DAT 995
    menu_six          DAT 996
    menu_seven        DAT 997
    menu_eight        DAT 998
    menu_nine         DAT 999
    # variables
    input             DAT 0
    list_size         DAT 0
    list              DAT
                      DAT
                      DAT
                      DAT
                      DAT
                      DAT
                      DAT
                      DAT
                      DAT
                      DAT
    
    
    
    <script src="https://cdn.jsdelivr.net/gh/trincot/lmc@v0.816/lmc.js"></script>

    You can run the code right here, using this snippet. Click Run Code Snippet to activate a LMC simulator, and then use the panel at the right side to interact with it.