WEBVTT

00:00:01.180 --> 00:00:05.490
Let's take a closer look at how macros work
in UASM.

00:00:05.490 --> 00:00:11.020
Here we see the definition of the macro "consec"
which has a single parameter "n".

00:00:11.020 --> 00:00:15.040
The body of the macro is a sequence of four
expressions.

00:00:15.040 --> 00:00:20.180
When there's an invocation of the "consec"
macro, in this example with the argument 37,

00:00:20.180 --> 00:00:27.150
the body of the macro is expanded replacing
all occurrences of "n" with the argument 37.

00:00:27.150 --> 00:00:33.480
The resulting text is then processed as if
it had appeared in place of the macro invocation.

00:00:33.480 --> 00:00:37.980
In this example, the four expressions are
evaluated to give a sequence of four values

00:00:37.980 --> 00:00:43.040
that will be placed in the next four bytes
of the output array.

00:00:43.040 --> 00:00:49.010
Macro expansions may contain other macro invocations,
which themselves will be expanded, continuing

00:00:49.010 --> 00:00:52.790
until all that's left are expressions to be
evaluated.

00:00:52.790 --> 00:00:57.899
Here we see the macro definition for WORD,
which assembles its argument into two consecutive

00:00:57.899 --> 00:00:59.170
bytes.

00:00:59.170 --> 00:01:04.430
And for the macro LONG, which assembles its
argument into four consecutive bytes, using

00:01:04.430 --> 00:01:10.000
the WORD macro to process the low 16 bits
of the value, then the high 16 bits of the

00:01:10.000 --> 00:01:11.000
value.

00:01:11.000 --> 00:01:17.539
These two UASM statements cause the constant
0xDEADBEEF to converted to 4 bytes, which

00:01:17.539 --> 00:01:23.190
are then deposited in the output array starting
at index 0x100.

00:01:23.190 --> 00:01:27.600
Note that the Beta expects the least-significant
byte of a multi-byte value to be stored at

00:01:27.600 --> 00:01:29.850
the lowest byte address.

00:01:29.850 --> 00:01:36.049
So the least-significant byte 0xEF is placed
at address 0x100 and the most-significant

00:01:36.049 --> 00:01:41.330
byte 0xDE is placed at address 0x103.

00:01:41.330 --> 00:01:45.799
This is the "little-endian" convention for
multi-byte values: the least-significant byte

00:01:45.799 --> 00:01:47.670
comes first.

00:01:47.670 --> 00:01:52.250
Intel's x86 architecture is also little-endian.

00:01:52.250 --> 00:01:58.110
There is a symmetrical "big-endian" convention
where the most-significant byte comes first.

00:01:58.110 --> 00:02:04.299
Both conventions are in active use and, in
fact, some ISAs can be configured to use either

00:02:04.299 --> 00:02:05.409
convention!

00:02:05.409 --> 00:02:09.889
There is no right answer for which convention
to use, but the fact that there two conventions

00:02:09.889 --> 00:02:15.189
means that we have to be alert for the need
to convert the representation of multi-byte

00:02:15.189 --> 00:02:21.999
values when moving values between one ISA
and another, e.g., when we send a data file

00:02:21.999 --> 00:02:24.569
to another user.

00:02:24.569 --> 00:02:28.909
As you can imagine there are strong advocates
for both schemes who are happy to defend their

00:02:28.909 --> 00:02:30.840
point of view at great length.

00:02:30.840 --> 00:02:36.400
Given the heat of the discussion, it's appropriate
that the names for the conventions were drawn

00:02:36.400 --> 00:02:41.900
from Jonathan Swift's "Gulliver's Travels"
in which a civil war is fought over whether

00:02:41.900 --> 00:02:47.269
to open a soft-boiled egg at its big end or
its little end.

00:02:47.269 --> 00:02:51.829
Let's look at the macros used to assemble
Beta instructions.

00:02:51.829 --> 00:02:57.559
The BETAOP helper macro supports the 3-register
instruction format, taking as arguments the

00:02:57.559 --> 00:03:04.109
values to be placed in the OPCODE, Ra, Rb,
and Rc fields.

00:03:04.109 --> 00:03:09.019
The ".align 4" directive is a bit of administrative
bookkeeping to ensure that instructions will

00:03:09.019 --> 00:03:15.670
have a byte address that's a multiple of 4,
i.e., that they span exactly one 32-bit word

00:03:15.670 --> 00:03:17.640
in memory.

00:03:17.640 --> 00:03:22.819
That's followed by an invocation of the LONG
macro to generate the 4 bytes of binary data

00:03:22.819 --> 00:03:26.760
representing the value of the expression shown
here.

00:03:26.760 --> 00:03:31.269
The expression is where the actual assembly
of the fields takes place.

00:03:31.269 --> 00:03:36.260
Each field is limited to requisite number
of bits using the modulo operator (%), then

00:03:36.260 --> 00:03:41.340
shifted left (<<) to the correct position
in the 32-bit word.

00:03:41.340 --> 00:03:48.290
And here are the helper macros for the instructions
that use a 16-bit constant as the second operand.

00:03:48.290 --> 00:03:52.699
Let's follow the assembly of an ADDC instruction
to see how this works.

00:03:52.699 --> 00:03:58.689
The ADDC macro expands into an invocation
of the BETAOPC helper macro, passing along

00:03:58.689 --> 00:04:04.799
the correct value for the ADDC opcode, along
with the three operands.

00:04:04.799 --> 00:04:11.439
The BETAOPC macro does the following arithmetic:
the OP argument, in this case the value 0x30,

00:04:11.439 --> 00:04:16.380
is shifted left to occupy the high-order 6
bits of the instruction.

00:04:16.380 --> 00:04:22.370
Then the RA argument, in this case 15, is
placed in its proper location.

00:04:22.370 --> 00:04:29.470
The 16-bit constant -32768 is positioned in
the low 16 bits of the instruction.

00:04:29.470 --> 00:04:38.080
And, finally, the Rc argument, in this case
0, is positioned in the Rc field of the instruction.

00:04:38.080 --> 00:04:42.270
You can see why we call this processing "assembling
an instruction".

00:04:42.270 --> 00:04:46.810
The binary representation of an instruction
is assembled from the binary values for each

00:04:46.810 --> 00:04:49.220
of the instruction fields.

00:04:49.220 --> 00:04:54.940
It's not a complicated process, but it requires
a lot of shifting and masking, tasks that

00:04:54.940 --> 00:04:57.939
we're happy to let a computer handle.

00:04:57.939 --> 00:05:04.069
Here's the entire sequence of macro expansions
that assemble this ADDC instruction into an

00:05:04.069 --> 00:05:08.460
appropriate 32-bit binary value in main memory.

00:05:08.460 --> 00:05:13.310
You can see that the knowledge of Beta instruction
formats and opcode values is built into the

00:05:13.310 --> 00:05:15.680
bodies of the macro definitions.

00:05:15.680 --> 00:05:19.280
The UASM processing is actually quite general.

00:05:19.280 --> 00:05:23.310
With a different set of macro definitions
it could process assembly language programs

00:05:23.310 --> 00:05:27.080
for almost any ISA!

00:05:27.080 --> 00:05:32.590
All the macro definitions for the Beta ISA
are provided in the beta.uasm file, which

00:05:32.590 --> 00:05:37.319
is included in each of the assembly language
lab assignments.

00:05:37.319 --> 00:05:42.099
Note that we include some convenience macros
to define shorthand representations that provide

00:05:42.099 --> 00:05:45.330
common default values for certain operands.

00:05:45.330 --> 00:05:51.110
For example, except for procedure calls, we
don't care about the PC+4 value saved in the

00:05:51.110 --> 00:05:58.090
destination register by branch instructions,
so almost always would specify R31 as the

00:05:58.090 --> 00:06:04.389
Rc register, effectively discarding the PC+4
value saved by branches.

00:06:04.389 --> 00:06:09.780
So we define two-argument branch macros that
automatically provide R31 as the destination

00:06:09.780 --> 00:06:10.780
register.

00:06:10.780 --> 00:06:16.060
Saves some typing, and, more importantly,
it makes it easier to understand the assembly

00:06:16.060 --> 00:06:18.449
language program.

00:06:18.449 --> 00:06:23.300
Here are a whole set of convenience macros
intended to make programs more readable.

00:06:23.300 --> 00:06:28.340
For example, unconditional branches can be
written using the BR() macro rather than the

00:06:28.340 --> 00:06:32.449
more cumbersome BEQ(R31,...).

00:06:32.449 --> 00:06:36.930
And it's more readable to use branch-false
(BF) and branch-true (BT) macros when testing

00:06:36.930 --> 00:06:40.120
the results of a compare instruction.

00:06:40.120 --> 00:06:43.669
And note the PUSH and POP macros at the bottom
of page.

00:06:43.669 --> 00:06:48.979
These expand into multi-instruction sequences,
in this case to add and remove values from

00:06:48.979 --> 00:06:54.020
a stack data structure pointed to by the SP
register.

00:06:54.020 --> 00:06:58.560
We call these macros "pseudo instructions"
since they let us provide the programmer with

00:06:58.560 --> 00:07:03.249
what appears a larger instruction set,
although underneath the covers we've just

00:07:03.249 --> 00:07:08.680
using the same small instruction repertoire
developed in Lecture 9.

00:07:08.680 --> 00:07:13.960
In this example we've rewritten the original
code we had for the factorial computation

00:07:13.960 --> 00:07:15.960
using pseudo instructions.

00:07:15.960 --> 00:07:22.009
For example, CMOVE is a pseudo instruction
for moving small constants into a register.

00:07:22.009 --> 00:07:27.080
It's easier for us to read and understand
the intent of a "constant move" operation

00:07:27.080 --> 00:07:33.599
than an "add a value to 0" operation provided
by the ADDC expansion of CMOVE.

00:07:33.599 --> 00:07:38.060
Anything we can do to remove the cognitive
clutter will be very beneficial in the long

00:07:38.060 --> 00:07:38.330
run.