Assembly DE1-SoC

Countdown clock Arm Assembly DE1-SoC system

Assembly was a very challenging class for me. The code conversion side of the class was very thought-provoking and rewarding. To challenge myself at the end of the semester, I attempted and successfully completed this project that compiles major topics covered in the class: from stacks to using branching and iterative techniques.

For a DE1-SoC system, I wrote an ARM assembly code that down counts from a decimal number
to 0 by 1 while displaying the numbers on the HEX3-HEX0 7 segments and simultaneously on the VGA screen. The starting number is determined by the positions of SW3-SW0, SW3 being the 1000’s position, SW2 being the 100s position, SW1 for 10s, and SW0 for units. Once the SW3-SW0 position determines the starting number, they are used no more. The counting-down process pauses when SW8 is pressed (i.e., SW8 = 1) and resumes when SW8 is released (SW8 = 0).

On the screen with the background colored by the pixel color determined by the last four-
digits of my Howard ID, I displayed the numbers horizontally at (10, 20) location of the monitor.

.data
.equ switches, 0xFF200040    // Address for the switches
.equ segment_address, 0xFF200020   // Address for 7-segment display
.equ vga_pixel_mem, 0xc8000000     // Address for VGA pixel memory
.equ vga_char_mem, 0xc9000080       // Address for VGA character memory

// Lookup table for 7-segment encoding
my_lookup:
    .word 0x3F3F3F3F, 0x3F3F3F06, 0x3F3F063F, 0x3F3F0606, 0x3F063F3F, 0x3F063F06, 0x3F06063F, 0x3F060606
    .word 0x063F3F3F, 0x063F3F06, 0x063F063F, 0x063F0606, 0x06063F3F, 0x06063F06, 0x0606063F, 0x06060606

// 7-segment digit patterns for numbers 0-9
count:
    .word 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7C, 0x07, 0x7F, 0x67

.text
.global _start
_start:

    // Initialize registers to zero
    mov r0, #0
    mov r1, #0
    mov r2, #0
    mov r3, #0
    mov r4, #0
    mov r5, #0
    mov r6, #0
    mov r7, #0
    mov r8, #0
    mov r9, #0
    mov r10, #0

    push {r1, r2, r3, r4}

    // Set initial values for VGA
    LDR R1, =vga_pixel_mem
    LDR R2, =0x3241      // Set pixel color from last 4 digits of ID: 3241
    MOV R3, #0
    MOV R4, #0

    // Loop through the pixels to fill the VGA screen
y_part:
    MOV R3, #0
x_part:
    BL draw_pixel
    ADD R3, R3, #1
    CMP R3, #0x140
    BNE x_part
    ADD R4, R4, #1
    CMP R4, #240
    BNE y_part
    B main

// Draw one pixel to the screen
draw_pixel:
    PUSH {R5}
    ADD R5, R3, LSL #1        // Calculate X offset
    ADD R5, R4, LSL #10       // Calculate Y offset
    STRH R2, [R1, R5]         // Store pixel color
    POP {R5}
    BX LR

// Initialize keys based on switch values
main:
    pop {r1, r2, r3, r4}
    LDR r0, =switches           // Load address of switch register
    LDR R2, =segment_address    // Load address of segment display
    ldr r4, =my_lookup          // Load lookup table for 7-segment encoding

    // Get value from switches
    LDR r1, [r0]
    mov r3, r1

    // Get the corresponding 7-segment pattern from the lookup table
    ldr r5, [r4, r3, lsl #2]
    mov r3, #1                // Counter for digits
    mov r6, #0                // Running total for decimal conversion

// Loop through the digits for conversion
loop:
    and r7, r5, #0xff000000
    CMP R3, #1
    BEQ extract_first_digit
    mul r9, r10, r8
    add r6, r6, r9
    mov r9, #0
    and r7, r5, #0x00ff0000
    CMP R3, #2
    BEQ extract_second_digit
    mov r9, #0
    and r7, r5, #0x0000ff00
    CMP R3, #3
    BEQ extract_third_digit
    mov r9, #0
    and r7, r5, #0x000000ff
    CMP R3, #4
    BEQ extract_fourth_digit
    mov r9, #0
    ldr r4, =count
    b countdown_main

// Extracts least significant byte (LSB)
extract_fourth_digit:
    ldr r8, =#1
    cmp r7, #0x06
    BEQ assign_one
    B assign_zero
	
// Extracts third digit from the right
extract_third_digit:
    ldr r8, =#10
    cmp r7, #0x0600
    BEQ assign_one
    B assign_zero
	
// Extracts second digit from the left
extract_second_digit:
    ldr r8, =#100
    cmp r7, #0x060000
    BEQ assign_one
    B assign_zero

// Extracts most significant byte (MSB)
extract_first_digit:
    ldr r8, =#1000
    cmp r7, #0x06000000
    BEQ assign_one
    B assign_zero

// Assigns 0 for false
assign_zero:
    mov r10, #0
    ADD R3, R3, #1
    b loop

// Assigns 1 for true
assign_one:
    mov r10, #1
    ADD R3, R3, #1
    b loop

// Main countdown function
countdown_main:
    ldr r7, [r0]
    cmp r7, #0x200
    bge pause                  // Pause if SW8 is pressed
    cmp r6, #-1
    beq stop                   // Stop if countdown reaches 0

    mov r8, #0                 // Counter for thousandth place
    mov r10, #0                // Counter for hundredth place
    mov r11, #0                // Counter for tenth place
    mov r12, #0                // Counter for ones place
    mov r3, r6

    cmp r6, #1000
    bge thousandth
    cmp r6, #100
    bge hundredth
    cmp r6, #10
    bge tenth
    b ones

// Convert to thousandth place
thousandth:
    cmp r3, #1000
    blt hundredth
    sub r3, r3, #1000
    add r8, r8, #1
    b thousandth

// Convert to hundredth place
hundredth:
    cmp r3, #100
    blt tenth
    sub r3, r3, #100
    add r10, r10, #1
    b hundredth

// Convert to tenth place
tenth:
    cmp r3, #10
    blt ones
    sub r3, r3, #10
    add r11, r11, #1
    b tenth

// Handle ones place conversion
ones:
    mov r12, r3
    push {r1, r3, r4, r5, r7, r9}
    ldr r1, =vga_pixel_mem
    ldr r4, =vga_char_mem
    mov r7, #1
    mov r9, #1
    b redo

new_ones:
    pop {r1, r3, r4, r5, r7, r9}
    ldr r8, [r4, r8, lsl #2]
    lsl r8, r8, #24

    ldr r10, [r4, r10, lsl #2]
    lsl r10, r10, #16

    ldr r11, [r4, r11, lsl #2]
    lsl r11, r11, #8

    ldr r12, [r4, r12, lsl #2]

    add r8, r8, r10
    add r8, r8, r11
    add r8, r8, r12
    str r8, [r2]

    sub r6, r6, #1
    BL refresh_time
    b countdown_main

// Pause function for when SW8 is pressed
pause:
    ldr r7, [r0]
    cmp r7, #0x200
    blt countdown_main
    bne pause
	
// Delay for display refresh
refresh_time:
    push {r1}
    LDR r1, =0x200000
keep:
    SUBS r1, r1, #1
    BNE keep
    pop {r1}
    BX LR

// Redo the decimal conversion and display
redo:
    mov r3, #0x30
    add r3, r3, r8
    bl char_output
    add r7, r7, #1

    mov r3, #0x30
    add r3, r3, r10
    bl char_output
    add r7, r7, #1

    mov r3, #0x30
    add r3, r3, r11
    bl char_output
    add r7, r7, #1

    mov r3, #0x30
    add r3, r3, r12
    bl char_output
    add r7, r7, #1

    mov r3, #32
    bl char_output
    b new_ones

// Helper function to output character to the screen
char_output:
    push {r5}
    mov r5, #0
    add r5, r9, lsl #7
    add r5, r5, r7
    strb r3, [r4, r5]
    pop {r5}
    bx lr

stop:
    b stop

The program retrieves the value of the switches (e.g., from 0xFF200040) and processes it to
display on the 7-segment display. The switch value is indexed into my lookup table to get a
corresponding 7-segment pattern, which is then displayed on the segment display. The countdown
timer decrements a value stored in r6. This value controls the countdown, which is displayed on
the VGA screen. The code checks the value of r6 (the countdown value) and converts it to a four–
digit decimal number by breaking it down into thousands, hundreds, tens, and one’s places. It does
so by comparing the value against thresholds (1000, 100, 10) and subtracting the appropriate
amounts. My program then iterates through the digits of the countdown value. Each digit is
extracted by comparing the value against predefined thresholds. Each digit is then assigned to a
corresponding place. If the countdown reaches zero, the program jumps to the stop label ending
the program.