2018-06-17 17:02:17 +00:00
|
|
|
|
---
|
2024-12-09 11:34:00 +00:00
|
|
|
|
name: "MIPS Assembly"
|
2018-07-09 01:32:51 +00:00
|
|
|
|
filename: MIPS.asm
|
2018-06-17 17:02:17 +00:00
|
|
|
|
contributors:
|
|
|
|
|
- ["Stanley Lim", "https://github.com/Spiderpig86"]
|
|
|
|
|
---
|
|
|
|
|
|
2018-07-09 01:16:51 +00:00
|
|
|
|
The MIPS (Microprocessor without Interlocked Pipeline Stages) Assembly language
|
|
|
|
|
is designed to work with the MIPS microprocessor paradigm designed by J. L.
|
|
|
|
|
Hennessy in 1981. These RISC processors are used in embedded systems such as
|
|
|
|
|
gateways and routers.
|
2018-06-17 17:02:17 +00:00
|
|
|
|
|
|
|
|
|
[Read More](https://en.wikipedia.org/wiki/MIPS_architecture)
|
|
|
|
|
|
2018-08-25 02:45:59 +00:00
|
|
|
|
```asm
|
2018-06-17 17:02:17 +00:00
|
|
|
|
# Comments are denoted with a '#'
|
|
|
|
|
|
|
|
|
|
# Everything that occurs after a '#' will be ignored by the assembler's lexer.
|
2018-06-24 18:00:07 +00:00
|
|
|
|
|
|
|
|
|
# Programs typically contain a .data and .text sections
|
|
|
|
|
|
2018-07-09 01:16:51 +00:00
|
|
|
|
.data # Section where data is stored in memory (allocated in RAM), similar to
|
2019-11-21 16:33:30 +00:00
|
|
|
|
# variables in higher-level languages
|
2018-06-24 18:00:07 +00:00
|
|
|
|
|
|
|
|
|
# Declarations follow a ( label: .type value(s) ) form of declaration
|
2018-08-12 23:17:57 +00:00
|
|
|
|
hello_world: .asciiz "Hello World\n" # Declare a null terminated string
|
2018-07-09 01:16:51 +00:00
|
|
|
|
num1: .word 42 # Integers are referred to as words
|
2019-11-21 16:33:30 +00:00
|
|
|
|
# (32-bit value)
|
2018-07-09 01:16:51 +00:00
|
|
|
|
|
2018-06-24 18:55:47 +00:00
|
|
|
|
arr1: .word 1, 2, 3, 4, 5 # Array of words
|
|
|
|
|
arr2: .byte 'a', 'b' # Array of chars (1 byte each)
|
2018-07-09 01:16:51 +00:00
|
|
|
|
buffer: .space 60 # Allocates space in the RAM
|
|
|
|
|
# (not cleared to 0)
|
2018-06-24 18:00:07 +00:00
|
|
|
|
|
|
|
|
|
# Datatype sizes
|
2018-06-24 18:55:47 +00:00
|
|
|
|
_byte: .byte 'a' # 1 byte
|
|
|
|
|
_halfword: .half 53 # 2 bytes
|
|
|
|
|
_word: .word 3 # 4 bytes
|
|
|
|
|
_float: .float 3.14 # 4 bytes
|
|
|
|
|
_double: .double 7.0 # 8 bytes
|
2018-06-24 18:00:07 +00:00
|
|
|
|
|
2020-02-07 15:01:00 +00:00
|
|
|
|
.align 2 # Memory alignment of data, where
|
|
|
|
|
# number indicates byte alignment
|
|
|
|
|
# in powers of 2. (.align 2
|
|
|
|
|
# represents word alignment since
|
|
|
|
|
# 2^2 = 4 bytes)
|
|
|
|
|
|
|
|
|
|
.text # Section that contains
|
|
|
|
|
# instructions and program logic
|
2018-07-09 01:16:51 +00:00
|
|
|
|
.globl _main # Declares an instruction label as
|
|
|
|
|
# global, making it accessible to
|
|
|
|
|
# other files
|
2018-06-24 18:55:47 +00:00
|
|
|
|
|
2020-02-07 15:01:00 +00:00
|
|
|
|
_main: # MIPS programs execute
|
|
|
|
|
# instructions sequentially, where
|
|
|
|
|
# the code under this label will be
|
|
|
|
|
# executed first
|
2018-06-24 18:55:47 +00:00
|
|
|
|
|
|
|
|
|
# Let's print "hello world"
|
2020-02-07 15:01:00 +00:00
|
|
|
|
la $a0, hello_world # Load address of string stored
|
|
|
|
|
# in memory
|
|
|
|
|
li $v0, 4 # Load the syscall value (number
|
|
|
|
|
# indicating which syscall to make)
|
|
|
|
|
syscall # Perform the specified syscall
|
|
|
|
|
# with the given argument ($a0)
|
2018-06-24 18:55:47 +00:00
|
|
|
|
|
|
|
|
|
# Registers (used to hold data during program execution)
|
2018-07-09 01:16:51 +00:00
|
|
|
|
# $t0 - $t9 # Temporary registers used for
|
|
|
|
|
# intermediate calculations inside
|
|
|
|
|
# subroutines (not saved across
|
|
|
|
|
# function calls)
|
|
|
|
|
|
|
|
|
|
# $s0 - $s7 # Saved registers where values are
|
|
|
|
|
# saved across subroutine calls.
|
|
|
|
|
# Typically saved in stack
|
|
|
|
|
|
|
|
|
|
# $a0 - $a3 # Argument registers for passing in
|
|
|
|
|
# arguments for subroutines
|
|
|
|
|
# $v0 - $v1 # Return registers for returning
|
|
|
|
|
# values to caller function
|
2018-06-24 18:55:47 +00:00
|
|
|
|
|
|
|
|
|
# Types of load/store instructions
|
2018-07-09 01:16:51 +00:00
|
|
|
|
la $t0, label # Copy the address of a value in
|
2020-02-07 15:01:00 +00:00
|
|
|
|
# memory specified by the label
|
|
|
|
|
# into register $t0
|
2018-06-24 18:55:47 +00:00
|
|
|
|
lw $t0, label # Copy a word value from memory
|
2018-07-09 01:16:51 +00:00
|
|
|
|
lw $t1, 4($s0) # Copy a word value from an address
|
2020-02-07 15:01:00 +00:00
|
|
|
|
# stored in a register with an
|
|
|
|
|
# offset of 4 bytes (addr + 4)
|
|
|
|
|
lb $t2, label # Copy a byte value to the
|
|
|
|
|
# lower order portion of
|
|
|
|
|
# the register $t2
|
2018-07-09 01:16:51 +00:00
|
|
|
|
lb $t2, 0($s0) # Copy a byte value from the source
|
|
|
|
|
# address in $s0 with offset 0
|
2018-06-24 18:55:47 +00:00
|
|
|
|
# Same idea with 'lh' for halfwords
|
|
|
|
|
|
2020-02-07 15:01:00 +00:00
|
|
|
|
sw $t0, label # Store word value into
|
|
|
|
|
# memory address mapped by label
|
2018-07-09 01:16:51 +00:00
|
|
|
|
sw $t0, 8($s0) # Store word value into address
|
2020-02-07 15:01:00 +00:00
|
|
|
|
# specified in $s0 and offset of
|
|
|
|
|
# 8 bytes
|
2018-06-24 18:55:47 +00:00
|
|
|
|
# Same idea using 'sb' and 'sh' for bytes and halfwords. 'sa' does not exist
|
2018-06-24 18:00:07 +00:00
|
|
|
|
|
2018-07-07 16:08:10 +00:00
|
|
|
|
### Math ###
|
|
|
|
|
_math:
|
|
|
|
|
# Remember to load your values into a register
|
2018-07-08 19:05:27 +00:00
|
|
|
|
lw $t0, num # From the data section
|
|
|
|
|
li $t0, 5 # Or from an immediate (constant)
|
2018-07-07 16:08:10 +00:00
|
|
|
|
li $t1, 6
|
2018-07-08 19:16:26 +00:00
|
|
|
|
add $t2, $t0, $t1 # $t2 = $t0 + $t1
|
|
|
|
|
sub $t2, $t0, $t1 # $t2 = $t0 - $t1
|
|
|
|
|
mul $t2, $t0, $t1 # $t2 = $t0 * $t1
|
2018-07-09 01:16:51 +00:00
|
|
|
|
div $t2, $t0, $t1 # $t2 = $t0 / $t1 (Might not be
|
2022-12-10 15:05:34 +00:00
|
|
|
|
# supported in some versions of MARS)
|
2020-02-07 15:01:00 +00:00
|
|
|
|
div $t0, $t1 # Performs $t0 / $t1. Get the
|
|
|
|
|
# quotient using 'mflo' and
|
|
|
|
|
# remainder using 'mfhi'
|
2018-07-08 19:05:27 +00:00
|
|
|
|
|
|
|
|
|
# Bitwise Shifting
|
2018-07-09 01:16:51 +00:00
|
|
|
|
sll $t0, $t0, 2 # Bitwise shift to the left with
|
|
|
|
|
# immediate (constant value) of 2
|
2020-02-07 15:01:00 +00:00
|
|
|
|
sllv $t0, $t1, $t2 # Shift left by a variable amount
|
|
|
|
|
# in register
|
2018-07-09 01:16:51 +00:00
|
|
|
|
srl $t0, $t0, 5 # Bitwise shift to the right (does
|
2020-02-07 15:01:00 +00:00
|
|
|
|
# not sign preserve, sign-extends
|
|
|
|
|
# with 0)
|
|
|
|
|
srlv $t0, $t1, $t2 # Shift right by a variable amount
|
|
|
|
|
# in a register
|
|
|
|
|
sra $t0, $t0, 7 # Bitwise arithmetic shift to
|
|
|
|
|
# the right (preserves sign)
|
2018-07-09 01:16:51 +00:00
|
|
|
|
srav $t0, $t1, $t2 # Shift right by a variable amount
|
|
|
|
|
# in a register
|
2018-07-08 19:05:27 +00:00
|
|
|
|
|
|
|
|
|
# Bitwise operators
|
2018-07-08 19:16:26 +00:00
|
|
|
|
and $t0, $t1, $t2 # Bitwise AND
|
|
|
|
|
andi $t0, $t1, 0xFFF # Bitwise AND with immediate
|
|
|
|
|
or $t0, $t1, $t2 # Bitwise OR
|
|
|
|
|
ori $t0, $t1, 0xFFF # Bitwise OR with immediate
|
|
|
|
|
xor $t0, $t1, $t2 # Bitwise XOR
|
|
|
|
|
xori $t0, $t1, 0xFFF # Bitwise XOR with immediate
|
|
|
|
|
nor $t0, $t1, $t2 # Bitwise NOR
|
2018-07-07 16:08:10 +00:00
|
|
|
|
|
2018-07-20 14:39:43 +00:00
|
|
|
|
## BRANCHING ##
|
|
|
|
|
_branching:
|
|
|
|
|
# The basic format of these branching instructions typically follow <instr>
|
|
|
|
|
# <reg1> <reg2> <label> where label is the label we want to jump to if the
|
|
|
|
|
# given conditional evaluates to true
|
2019-11-21 16:33:30 +00:00
|
|
|
|
# Sometimes it is easier to write the conditional logic backward, as seen
|
2018-07-20 14:39:43 +00:00
|
|
|
|
# in the simple if statement example below
|
|
|
|
|
|
|
|
|
|
beq $t0, $t1, reg_eq # Will branch to reg_eq if
|
|
|
|
|
# $t0 == $t1, otherwise
|
|
|
|
|
# execute the next line
|
|
|
|
|
bne $t0, $t1, reg_neq # Branches when $t0 != $t1
|
2020-02-07 15:01:00 +00:00
|
|
|
|
b branch_target # Unconditional branch, will
|
|
|
|
|
# always execute
|
2018-07-20 14:39:43 +00:00
|
|
|
|
beqz $t0, req_eq_zero # Branches when $t0 == 0
|
|
|
|
|
bnez $t0, req_neq_zero # Branches when $t0 != 0
|
|
|
|
|
bgt $t0, $t1, t0_gt_t1 # Branches when $t0 > $t1
|
|
|
|
|
bge $t0, $t1, t0_gte_t1 # Branches when $t0 >= $t1
|
|
|
|
|
bgtz $t0, t0_gt0 # Branches when $t0 > 0
|
|
|
|
|
blt $t0, $t1, t0_gt_t1 # Branches when $t0 < $t1
|
|
|
|
|
ble $t0, $t1, t0_gte_t1 # Branches when $t0 <= $t1
|
|
|
|
|
bltz $t0, t0_lt0 # Branches when $t0 < 0
|
2023-03-24 20:33:35 +00:00
|
|
|
|
slt $s0, $t0, $t1 # "Set on Less Than"
|
2020-02-07 15:01:00 +00:00
|
|
|
|
# when $t0 < $t1 with result in $s0
|
|
|
|
|
# (1 for true)
|
2018-07-20 14:39:43 +00:00
|
|
|
|
|
|
|
|
|
# Simple if statement
|
|
|
|
|
# if (i == j)
|
|
|
|
|
# f = g + h;
|
|
|
|
|
# f = f - i;
|
|
|
|
|
|
|
|
|
|
# Let $s0 = f, $s1 = g, $s2 = h, $s3 = i, $s4 = j
|
|
|
|
|
bne $s3, $s4, L1 # if (i !=j)
|
|
|
|
|
add $s0, $s1, $s2 # f = g + h
|
|
|
|
|
|
|
|
|
|
L1:
|
|
|
|
|
sub $s0, $s0, $s3 # f = f - i
|
|
|
|
|
|
|
|
|
|
# Below is an example of finding the max of 3 numbers
|
|
|
|
|
# A direct translation in Java from MIPS logic:
|
|
|
|
|
# if (a > b)
|
|
|
|
|
# if (a > c)
|
|
|
|
|
# max = a;
|
|
|
|
|
# else
|
|
|
|
|
# max = c;
|
|
|
|
|
# else
|
2023-03-24 20:35:30 +00:00
|
|
|
|
# if (b > c)
|
2018-07-20 14:39:43 +00:00
|
|
|
|
# max = b;
|
|
|
|
|
# else
|
|
|
|
|
# max = c;
|
|
|
|
|
|
|
|
|
|
# Let $s0 = a, $s1 = b, $s2 = c, $v0 = return register
|
2020-02-07 15:01:00 +00:00
|
|
|
|
ble $s0, $s1, a_LTE_b # if(a <= b) branch(a_LTE_b)
|
|
|
|
|
ble $s0, $s2, max_C # if(a > b && a <=c) branch(max_C)
|
2022-03-03 07:44:38 +00:00
|
|
|
|
move $v0, $s0 # else [a > b && a > c] max = a
|
2018-07-20 14:39:43 +00:00
|
|
|
|
j done # Jump to the end of the program
|
|
|
|
|
|
|
|
|
|
a_LTE_b: # Label for when a <= b
|
2020-02-07 15:01:00 +00:00
|
|
|
|
ble $s1, $s2, max_C # if(a <= b && b <= c) branch(max_C)
|
|
|
|
|
move $v0, $s1 # if(a <= b && b > c) max = b
|
2018-07-20 14:39:43 +00:00
|
|
|
|
j done # Jump to done
|
|
|
|
|
|
|
|
|
|
max_C:
|
|
|
|
|
move $v0, $s2 # max = c
|
|
|
|
|
|
|
|
|
|
done: # End of program
|
|
|
|
|
|
2018-08-05 19:25:44 +00:00
|
|
|
|
## LOOPS ##
|
|
|
|
|
_loops:
|
2018-08-07 03:34:56 +00:00
|
|
|
|
# The basic structure of loops is having an exit condition and a jump
|
2020-05-08 07:29:38 +00:00
|
|
|
|
# instruction to continue its execution
|
2018-08-05 19:25:44 +00:00
|
|
|
|
li $t0, 0
|
|
|
|
|
while:
|
2023-03-24 20:43:38 +00:00
|
|
|
|
bgt $t0, 9, end_while # While $t0 is less than 10,
|
2020-02-07 15:01:00 +00:00
|
|
|
|
# keep iterating
|
2023-03-24 20:43:38 +00:00
|
|
|
|
#actual loop content would go here
|
2018-08-05 19:25:44 +00:00
|
|
|
|
addi $t0, $t0, 1 # Increment the value
|
2020-02-07 15:01:00 +00:00
|
|
|
|
j while # Jump back to the beginning of
|
|
|
|
|
# the loop
|
2018-08-05 19:25:44 +00:00
|
|
|
|
end_while:
|
|
|
|
|
|
|
|
|
|
# 2D Matrix Traversal
|
|
|
|
|
# Assume that $a0 stores the address of an integer matrix which is 3 x 3
|
|
|
|
|
li $t0, 0 # Counter for i
|
|
|
|
|
li $t1, 0 # Counter for j
|
|
|
|
|
matrix_row:
|
|
|
|
|
bgt $t0, 3, matrix_row_end
|
|
|
|
|
|
|
|
|
|
matrix_col:
|
|
|
|
|
bgt $t1, 3, matrix_col_end
|
|
|
|
|
|
|
|
|
|
# Do stuff
|
|
|
|
|
|
2018-08-07 03:34:56 +00:00
|
|
|
|
addi $t1, $t1, 1 # Increment the col counter
|
2018-08-05 19:25:44 +00:00
|
|
|
|
matrix_col_end:
|
|
|
|
|
|
|
|
|
|
# Do stuff
|
|
|
|
|
|
|
|
|
|
addi $t0, $t0, 1
|
|
|
|
|
matrix_row_end:
|
|
|
|
|
|
2018-08-07 03:34:56 +00:00
|
|
|
|
## FUNCTIONS ##
|
|
|
|
|
_functions:
|
|
|
|
|
# Functions are callable procedures that can accept arguments and return
|
2021-08-26 14:48:24 +00:00
|
|
|
|
# values all denoted with labels, like above
|
2018-08-07 03:34:56 +00:00
|
|
|
|
|
|
|
|
|
main: # Programs begin with main func
|
|
|
|
|
jal return_1 # jal will store the current PC in $ra
|
|
|
|
|
# and then jump to return_1
|
|
|
|
|
|
|
|
|
|
# What if we want to pass in args?
|
|
|
|
|
# First we must pass in our parameters to the argument registers
|
|
|
|
|
li $a0, 1
|
|
|
|
|
li $a1, 2
|
|
|
|
|
jal sum # Now we can call the function
|
|
|
|
|
|
|
|
|
|
# How about recursion?
|
|
|
|
|
# This is a bit more work since we need to make sure we save and restore
|
2020-02-07 15:01:00 +00:00
|
|
|
|
# the previous PC in $ra since jal will automatically overwrite
|
|
|
|
|
# on each call
|
2018-08-07 03:34:56 +00:00
|
|
|
|
li $a0, 3
|
|
|
|
|
jal fact
|
|
|
|
|
|
|
|
|
|
li $v0, 10
|
|
|
|
|
syscall
|
|
|
|
|
|
|
|
|
|
# This function returns 1
|
|
|
|
|
return_1:
|
|
|
|
|
li $v0, 1 # Load val in return register $v0
|
|
|
|
|
jr $ra # Jump back to old PC to continue exec
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Function with 2 args
|
|
|
|
|
sum:
|
|
|
|
|
add $v0, $a0, $a1
|
|
|
|
|
jr $ra # Return
|
|
|
|
|
|
|
|
|
|
# Recursive function to find factorial
|
|
|
|
|
fact:
|
|
|
|
|
addi $sp, $sp, -8 # Allocate space in stack
|
|
|
|
|
sw $s0, ($sp) # Store reg that holds current num
|
|
|
|
|
sw $ra, 4($sp) # Store previous PC
|
|
|
|
|
|
|
|
|
|
li $v0, 1 # Init return value
|
|
|
|
|
beq $a0, 0, fact_done # Finish if param is 0
|
|
|
|
|
|
|
|
|
|
# Otherwise, continue recursion
|
|
|
|
|
move $s0, $a0 # Copy $a0 to $s0
|
|
|
|
|
sub $a0, $a0, 1
|
|
|
|
|
jal fact
|
|
|
|
|
|
|
|
|
|
mul $v0, $s0, $v0 # Multiplication is done
|
|
|
|
|
|
|
|
|
|
fact_done:
|
|
|
|
|
lw $s0, ($sp)
|
2021-12-22 19:40:43 +00:00
|
|
|
|
lw $ra, 4($sp) # Restore the PC
|
2018-08-07 03:34:56 +00:00
|
|
|
|
addi $sp, $sp, 8
|
|
|
|
|
|
|
|
|
|
jr $ra
|
|
|
|
|
|
2018-08-08 01:48:42 +00:00
|
|
|
|
## MACROS ##
|
|
|
|
|
_macros:
|
2019-11-21 16:33:30 +00:00
|
|
|
|
# Macros are extremely useful for substituting repeated code blocks with a
|
2018-08-08 01:48:42 +00:00
|
|
|
|
# single label for better readability
|
|
|
|
|
# These are in no means substitutes for functions
|
|
|
|
|
# These must be declared before it is used
|
|
|
|
|
|
2019-11-21 16:33:30 +00:00
|
|
|
|
# Macro for printing newlines (since these can be very repetitive)
|
2018-08-08 01:48:42 +00:00
|
|
|
|
.macro println()
|
|
|
|
|
la $a0, newline # New line string stored here
|
|
|
|
|
li $v0, 4
|
|
|
|
|
syscall
|
|
|
|
|
.end_macro
|
|
|
|
|
|
|
|
|
|
println() # Assembler will copy that block of
|
|
|
|
|
# code here before running
|
|
|
|
|
|
|
|
|
|
# Parameters can be passed in through macros.
|
|
|
|
|
# These are denoted by a '%' sign with any name you choose
|
|
|
|
|
.macro print_int(%num)
|
|
|
|
|
li $v0, 1
|
|
|
|
|
lw $a0, %num
|
|
|
|
|
syscall
|
|
|
|
|
.end_macro
|
|
|
|
|
|
|
|
|
|
li $t0, 1
|
|
|
|
|
print_int($t0)
|
|
|
|
|
|
|
|
|
|
# We can also pass in immediates for macros
|
|
|
|
|
.macro immediates(%a, %b)
|
|
|
|
|
add $t0, %a, %b
|
|
|
|
|
.end_macro
|
|
|
|
|
|
|
|
|
|
immediates(3, 5)
|
|
|
|
|
|
|
|
|
|
# Along with passing in labels
|
|
|
|
|
.macro print(%string)
|
|
|
|
|
la $a0, %string
|
|
|
|
|
li $v0, 4
|
|
|
|
|
syscall
|
|
|
|
|
.end_macro
|
2018-08-07 03:34:56 +00:00
|
|
|
|
|
2018-08-08 01:48:42 +00:00
|
|
|
|
print(hello_world)
|
2018-08-09 02:01:56 +00:00
|
|
|
|
|
|
|
|
|
## ARRAYS ##
|
|
|
|
|
.data
|
|
|
|
|
list: .word 3, 0, 1, 2, 6 # This is an array of words
|
|
|
|
|
char_arr: .asciiz "hello" # This is a char array
|
|
|
|
|
buffer: .space 128 # Allocates a block in memory, does
|
|
|
|
|
# not automatically clear
|
|
|
|
|
# These blocks of memory are aligned
|
2019-11-21 16:33:30 +00:00
|
|
|
|
# next to each other
|
2018-08-09 02:01:56 +00:00
|
|
|
|
|
|
|
|
|
.text
|
|
|
|
|
la $s0, list # Load address of list
|
|
|
|
|
li $t0, 0 # Counter
|
|
|
|
|
li $t1, 5 # Length of the list
|
|
|
|
|
|
|
|
|
|
loop:
|
2021-12-22 19:40:43 +00:00
|
|
|
|
bge $t0, $t1, end_loop
|
2018-08-09 02:01:56 +00:00
|
|
|
|
|
|
|
|
|
lw $a0, ($s0)
|
|
|
|
|
li $v0, 1
|
|
|
|
|
syscall # Print the number
|
|
|
|
|
|
|
|
|
|
addi $s0, $s0, 4 # Size of a word is 4 bytes
|
|
|
|
|
addi $t0, $t0, 1 # Increment
|
|
|
|
|
j loop
|
|
|
|
|
end_loop:
|
|
|
|
|
|
2018-08-10 01:26:46 +00:00
|
|
|
|
## INCLUDE ##
|
2018-08-10 01:30:41 +00:00
|
|
|
|
# You do this to import external files into your program (behind the scenes,
|
2018-08-12 23:17:57 +00:00
|
|
|
|
# it really just takes whatever code that is in that file and places it where
|
|
|
|
|
# the include statement is)
|
2018-08-10 01:26:46 +00:00
|
|
|
|
.include "somefile.asm"
|
2018-07-20 14:39:43 +00:00
|
|
|
|
```
|