guide
guide
Abstract
The study of C and assembly language can provide valuable insight about the innate
nature of computing systems and higher level programming languages. However, be-
fore September 2022, the MIT Department of Electrical Engineering and Computer
Science (MIT EECS) had not required students to take any class that covers this ma-
terial and these relationships. The classes included in the introductory programming
sequence taken by most MIT EECS students place a stronger emphasis on high-level
languages such as Python, which abstract away the interactions that a program must
have with memory. Previously, if C had been introduced in an introductory-level
class, it was one of several simultaneous concepts being taught to the students and
therefore was not explored in depth. In September 2022, MIT EECS revised the
class requirements for two of its degrees, Electrical Engineering and Computer Sci-
ence (Course 6-2) and Computer Science and Engineering (Course 6-3) [1] to require
a six-unit introductory course that focuses on low-level programming using C and
assembly language. This thesis focuses on the establishment of this introductory
low-level programming class intended for students positioned early in the EECS cur-
riculum. Students taking this class study C and assembly language so that they can
enter later coursework with both the ability to use these programming languages and
a basic understanding of computing systems and associated constraints.
3
4
Acknowledgments
My involvement in Introduction to Low-level Programming in C and Assembly (6.190)
is largely due to the support of two people instrumental to its development – Joseph
Steinmeyer and Silvina Hanono Wachman. Thank you both for the countless contri-
butions you have made to this class, and for always welcoming my ideas, valuing my
feedback, and providing guidance. I have enjoyed collaborating with you, and I will
look back fondly on these two years.
This thesis would be telling a different story if not for the person who presented
me with this topic in the first place. Joe, thank you for providing me with this
unique opportunity, from which I have been able to learn valuable lessons about
course development and education.
Another significant component of my MEng was working as a teaching assistant
for 6.191 (previously 6.004) for three semesters. Silvina, thank you for granting me
that opportunity, allowing me to play a part in introducing students to the world of
digital computing systems.
One major takeaway from this experience is that it takes many people to run a
large class smoothly, especially when it has an in-class laboratory component. Thank
you to all of the instructors, teaching assistants, and laboratory assistants for all
of your hard work, feedback, and contributions throughout the first five offerings of
6.190. It has been a pleasure working with you all!
Thank you to my friends – particularly Daria Bakhtiari, Julia Keenan, Rachel
Prevost, Sarah Berube, and Veronica Walsh – for all of the memories, laughs, and
fun over the years. You all inspire me, and I am thankful to have you in my life.
Finally, thank you to my parents for supporting me in all of my endeavors. I am
grateful for all of the sacrifices you have made to ensure my siblings and I each have
the opportunity to realize our fullest potential.
5
6
Contents
1 Background 19
1.1 Massachusetts Institute of Technology (MIT) . . . . . . . . . . . . . . 20
1.1.1 Old Curriculum (2017-2022) . . . . . . . . . . . . . . . . . . . 20
1.1.2 New Curriculum (2022-) . . . . . . . . . . . . . . . . . . . . . 22
1.2 Improvements upon Existing MIT Courses . . . . . . . . . . . . . . . 23
2 Course Content 25
2.1 C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.2 Assembly Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.3 Course Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.3.1 Lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.2 Recitation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.4 Laboratory-Based Assignments (Labs and Postlabs) . . . . . . 30
2.3.5 Final Exam . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3 Laboratory Component 33
3.1 Laboratory Infrastructure . . . . . . . . . . . . . . . . . . . . . . . . 33
3.1.1 Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.1.2 Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.2 Laboratory Assignments . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.2.1 Lab 1: Introduction . . . . . . . . . . . . . . . . . . . . . . . . 38
3.2.2 Postlab 1: Hit the Bit . . . . . . . . . . . . . . . . . . . . . . 40
7
3.2.3 Lab 2: ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.2.4 Postlab 2: Scrolling Text . . . . . . . . . . . . . . . . . . . . . 44
3.2.5 Lab 3: Snake I . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.2.6 Postlab 3: Snake II . . . . . . . . . . . . . . . . . . . . . . . . 50
3.2.7 Lab 4: Introduction to Assembly . . . . . . . . . . . . . . . . 51
3.2.8 Postlab 4: Bubble Sort . . . . . . . . . . . . . . . . . . . . . . 53
3.2.9 Lab 5: Game of Life . . . . . . . . . . . . . . . . . . . . . . . 54
3.2.10 Postlab 5: Quicksort . . . . . . . . . . . . . . . . . . . . . . . 57
5 Discussion 77
5.1 Enrollment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.2 Time Commitment . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.3 Effectiveness of Course Components . . . . . . . . . . . . . . . . . . . 80
5.4 Learning Objectives . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
6 Future Work 91
6.1 Stable Toolchain for Laboratory Assignments . . . . . . . . . . . . . 91
6.2 C Debugging Tool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
6.3 Full-Semester Version of 6.190 . . . . . . . . . . . . . . . . . . . . . . 92
6.4 Analyzing Impact on Follow-on Classes . . . . . . . . . . . . . . . . . 93
A Code Appendix 95
A.1 Lab 1 Starter Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
A.1.1 Main C File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
8
A.1.2 6.190 Header File . . . . . . . . . . . . . . . . . . . . . . . . . 96
A.2 Postlab 1 Starter Code . . . . . . . . . . . . . . . . . . . . . . . . . . 102
A.2.1 Main C File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
A.2.2 6.190 Header File Code for Driving LED Display . . . . . . . 104
A.3 Lab 2 Starter Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
A.3.1 Main C File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
A.3.2 ASCII Header File . . . . . . . . . . . . . . . . . . . . . . . . 110
A.4 Postlab 2 Starter Code . . . . . . . . . . . . . . . . . . . . . . . . . . 116
A.4.1 Main C File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
A.5 Lab 3 Starter Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
A.5.1 Main C File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
A.6 Lab 4 Starter Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
A.6.1 Main C File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
A.6.2 Assembly Language File for Hardware-Related Procedures . . 128
A.7 Postlab 4 Starter Code . . . . . . . . . . . . . . . . . . . . . . . . . . 130
A.7.1 Main C File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
A.7.2 Bubble Sort Assembly File . . . . . . . . . . . . . . . . . . . . 132
A.8 Lab 5 Starter Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
A.8.1 Main C File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
A.8.2 Game of Life Assembly File . . . . . . . . . . . . . . . . . . . 134
A.9 Postlab 5 Starter Code . . . . . . . . . . . . . . . . . . . . . . . . . . 137
A.9.1 Main C File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
A.9.2 Quicksort Assembly File . . . . . . . . . . . . . . . . . . . . . 139
9
10
List of Figures
1-1 Relevant courses in the pre-2022 MIT EECS curriculum, which was
introduced in 2017. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1-2 Relevant courses in the current MIT EECS curriculum, which was
introduced in 2022. Notably, 6.190 was inserted to be taken between
6.100A and 6.191. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3-2 The printed circuit board (PCB), containing six buttons and eight
switches, designed specifically for use in the 6.190 embedded system. . 36
3-5 Example of typical Postlab 1 gameplay. Two switches, SW6 and SW1,
are in the incorrect positions, while the rest are correct. As a result, the
LED corresponding with SW6 will turn on, and the LED corresponding
with SW1 will stay on. The rest of the LEDs will become or remain
turned off. Then, all of the LEDs will shift to the right, and the
rightmost LED will wrap around to the leftmost column. . . . . . . . 42
3-6 Lab 2 system displaying the message “RISC” on the 8x32 LED array. 43
11
3-7 In Lab 2, switch inputs on the lab kit are interpreted as eight-bit binary
numbers and ASCII characters. . . . . . . . . . . . . . . . . . . . . . 44
3-9 Using the values from the original ASCII header file directly resulted in
letters appearing flipped on our LED display. The values needed to be
reversed in order for the letter to appear correctly. Students originally
had to use a helper function in their program to reverse the bits in
each value they used from the file, but in later iterations of the course
we provided them with an updated ascii.h file with the entries flipped
for them. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3-11 In Lab 3 and Postlab 3, locations on the 8x32 LED display are repre-
sented using a single unsigned eight-bit variable, with the upper five
bits encoding the column and the lower three bits encoding the row. . 49
3-12 The Game of Life rules [2], applied four times to two initial configura-
tions of an 8x8 game board. The Loaf configuration does not change
in response to the rules, while the Glider configuration does. . . . . . 55
4-2 The initial state of a RISC-V debugger instance, prior to executing any
of the program’s instructions. The highlighted instruction is the one
that is about to be executed. . . . . . . . . . . . . . . . . . . . . . . . 62
12
4-4 The RISC-V assembly debugger highlights either the memory address
either modified by the instruction executed most recently or an address
explicitly searched for by the user. Here, the user had searched for the
memory address 0x724, so the contents of that and the surrounding
memory addresses are shown. . . . . . . . . . . . . . . . . . . . . . . 63
4-5 The RISC-V assembly debugger displays the stack section of memory
separately, highlighting the element added to the stack by the previous
instruction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4-7 The structure of the assembly code used for the initial scan to identify
breakpoints and distinguish the assembly instructions submitted by
the student from the assembly instructions injected by course staff. . 68
4-9 An example entry in the program state JSON object, where the pre-
vious instruction had modified register a0. This information is com-
municated via the updated_register and new_reg_vals fields in the
entry. Note that this is the state of the system before the instruction
included in this entry, addi sp, sp, -4, is executed. . . . . . . . . . 71
4-10 An example entry in the program state JSON object, where the previ-
ous instruction had written the value 1 to the memory address 524788.
This is communicated through the updated_addr and new_mem_vals
fields in the entry. This memory address modified was also a part of the
stack, which is determined using the fields stack_top and stack_bottom,
so the stack_data field is also updated to reflect this memory write. 71
13
4-11 An example entry in the program state JSON object, where the current
instruction is a csbreak instruction, indicating that the program has
reached a breakpoint. Due to the way the state of the system is ren-
dered on the front-end of the debugger, the entire state of the system
must be logged at each breakpooint. . . . . . . . . . . . . . . . . . . 72
4-12 An example of the JavaScript object containing data about the current
state of the system that is used to render the front-end of the debugger
at that point in time. . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
14
5-4 Histograms displaying the frequencies of student responses to the sev-
enth through twelfth final survey prompts pertaining to their perceived
understanding of main course topics and comfort with using them. A
response of 0 indicates that the respondent strongly disagreed with the
statement, a 2 indicates that the respondent was neutral towards the
statement, and a 4 indicates that the respondent strongly agreed with
the statement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5-5 Histogram displaying the frequencies of student responses to the thir-
teenth final survey prompt pertaining to their perceived understanding
of main course topics and comfort with using them. A response of 0
indicates that the respondent strongly disagreed with the statement,
a 2 indicates that the respondent was neutral towards the statement,
and a 4 indicates that the respondent strongly agreed with the statement. 90
15
16
List of Tables
2.1 The topics covered in 6.190 are distributed over six weeks, because the
course is offered during half of an academic semester at MIT. . . . . 29
5.2 The average self-reported amount of time (in hours) that a student
would spend on Introduction to Low-level Programming in C and As-
sembly weekly. The overall average is provided, as well as averages
for each week and course component. The data pertaining to the time
spent on exercises, labs, and postlabs was collected from weekly surveys
administered to students during the first three offerings of the course.
We assume that the average student attends the 1.5-hour lecture and
1.5-hour recitation each week. . . . . . . . . . . . . . . . . . . . . . . 81
17
5.3 Student responses to a series of prompts pertaining to their experience
with the different components of the class. This data was collected from
a survey administered to students at the end of each of the first three
offerings of the class. A response of 0 indicates that the respondent
strongly disagreed with the statement, a 2 indicates that the respon-
dent was neutral towards the statement, and a 4 indicates that the
respondent strongly agreed with the statement. The responses shown
in the table imply that students, on average, found the hands-on por-
tions of the class, such as laboratory assignments and exercises, more
useful to their overall understanding of the course material than the
other portions, such as lecture and recitation. . . . . . . . . . . . . . 84
5.4 Student responses to a series of prompts pertaining to their perceived
understanding of main course topics and comfort with using them. A
response of 0 indicates that the respondent strongly disagreed with the
statement, a 2 indicates that the respondent was neutral towards the
statement, and a 4 indicates that the respondent strongly agreed with
the statement. These responses were gathered via a survey adminis-
tered at the end of each of the first three offerings of the course. . . . 86
18
Chapter 1
Background
There are various software applications, such as systems-level programming and em-
bedded firmware development, where it is critical for a programmer to have control
over computing resources such as memory. The C programming language is widely
used for these applications due to its nature as a low-level language that allows pro-
grammers access to these resources while still being compatible with various computer
architectures. After a programmer writes a C program, it is compiled into an assem-
bly language program, which is made up of the basic instructions that can be directly
understood and executed by a computer. Different architectures understand different
sets of instructions, so the set of assembly language instructions that a C program
is ultimately translated into depends on the architecture of the computer being pro-
grammed. As a result of this dependence and the basic nature of assembly language
instructions, assembly language is used much less frequently by programmers than
C. This necessary translation of C programs to assembly language programs is done
by sophisticated tools such as compilers. However, the study of assembly language
provides valuable insights into how a program is ultimately translated into the binary
data that is stored in memory, as well as how a program is executed by a processor.
Although exposure to low-level programming can deepen one’s understanding of
software programming and underlying computing systems, the MIT Department of
Electrical Engineering and Computer Science (MIT EECS) has not historically re-
quired its students to take a course dedicated to it. However, in 2022, the department
19
updated the curricula for some of its majors. One significant change made during
this update was the inclusion of a half-semester class that specifically focused on low-
level programming [1]. This course is required of all students completing certain MIT
EECS majors, and it is a prerequisite to a foundational digital systems and computer
architecture course.
This thesis discusses the development of this new low-level programming course,
named Introduction to Low-level Programming in C and Assembly (6.190), which has
been offered at MIT during the last five academic quarters, starting in the second
half of the spring 2022 semester. In the future, it will be offered during both halves
of just the spring semester (the third and fourth quarters of the academic year). This
course focuses on exploring various low-level programming topics, such as memory,
binary encoding, bit manipulation, using C and assembly language. We developed a
six-week curriculum that covers these topics, and we designed various hardware and
software tools to enhance students’ understanding of material covered in the course.
Prior to 2022, the MIT Department of Electrical Engineering and Computer Science
had three core majors: Electrical Science and Engineering (Course 6-1), Electrical
Engineering and Computer Science (Course 6-2), and Computer Science and Engi-
neering (Course 6-3). The most recent curriculum, established in 2017, included a
programming track containing three courses, none of which explicitly covered low-level
programming. This programming track is shown in Figure 1-1. The introductory pro-
20
gramming course, required of students in all three EECS majors, was Introduction to
Computer Science Programming in Python (6.0001). The following two courses were
strictly required of computer science students, and they could fulfill requirements for
students in the other two majors. The first class, Fundamentals of Programming
(6.009), also used Python almost exclusively, and the second, Software Construction
(6.031), used Java, but was updated to utilize TypeScript in 2022.
Figure 1-1: Relevant courses in the pre-2022 MIT EECS curriculum, which was in-
troduced in 2017.
Other introductory-level courses utilized low-level languages, but they did not
focus on them. Students in all three majors were required to take Computation
Structures (6.004), a foundational course that introduced digital systems and com-
puter architecture. It had just 6.0001 as a programming prerequisite, as shown in
Figure 1-1. So, students who had taken just this class had only basic familiarity
with Python, a high-level programming language. In order to provide the necessary
background for the computer architecture portion of the course, the early weeks of
6.004 focused on binary encoding and RISC-V assembly language, bypassing the C
programming language entirely. While it was critical for 6.004 to cover binary encod-
ing and RISC-V assembly language, the time spent on those topics left little room for
the class to cover more complex topics, such as operating systems. As a result, those
topics were only lightly covered. Additionally, topics such as assembly language and
operating systems could have been more effectively and efficiently taught if students
21
had some working knowledge of the C language.
Additionally, another course, named Interconnected Embedded Systems (6.08),
commonly fulfilled the “Introductory Subject” requirement for all three majors, and
it used a mix of C and C++, a superset of C, as a tool to program an Arduino-based
microcontroller. While students gained familiarity with the language, they frequently
relied on various external libraries and packages that abstracted away many details
of low-level programming, including how the programming language would directly
interact with the physical hardware. Additionally, the course was extremely broad. It
covered a wide variety of topics, such as the Internet of Things (IoT) and databases,
and it also required students to use high-level programming languages such as Python
and SQL. While students did gain exposure to C, the course did not provide a solid
foundation in the language and associated low-level programming topics.
Because no introductory-level or required course adequately covered low-level pro-
gramming, upper-level courses that relied on C or assembly language needed to in-
troduce the languages as they were needed, either by dedicating class time to cov-
ering them or requiring students to learn them on their own outside of class. These
classes would not focus on the fundamentals of either language, but rather on how
the languages could be applied to a given area. As a result, students could poten-
tially complete these classes, and graduate with an MIT EECS degree, without truly
understanding low-level programming.
In 2022, the department revised the curricula for the Electrical Engineering and
Computer Science major (Course 6-2) and the Computer Science and Engineering
major (Course 6-3). Additionally, the department updated all of its subject numbers
in the fall of 2022.1 The required programming track subjects did not change, as
1
All MIT EECS courses were assigned updated numbers, starting in the fall 2022 semester. In
the programming track, 6.0001 (Introduction to Computer Science Programming in Python) became
6.100A, 6.009 (Fundamentals of Programming) became 6.101, and 6.031 (Software Construction)
became 6.102. Additionally, 6.004 (Computation Structures) became 6.191, 6.08 (Interconnected
Embedded Systems) became 6.901, and 6.0004 (Introduction to Low-level Programming in C and
Assembly, the subject of this thesis) became 6.190.
22
shown in Figure 1-2, and it continues to focus on programming using higher level
languages.
Although the main programming track remained the same, the department added
a required six-unit, half-semester course, Introduction to Low-level Programming in
C and Assembly (6.190, previously 6.0004). This course focuses on low-level program-
ming via both C and assembly language, and it serves as a prerequisite course for
Computation Structures (6.191, previously 6.004). 6.191 no longer explicitly teaches
assembly language because it is taught in this new course. Students are expected to
have taken the first course in the programming sequence, 6.100A (previously 6.0001)
or equivalent before taking this class, so this course will not need to cover basic pro-
gramming skills. The order of these courses in the new curriculum is shown in Figure
1-2. Because 6.191 is a prerequisite course for most classes that cover systems-level
programming, and the new course is a prerequisite for 6.191, students will enter those
upper-level classes with an understanding of basic low-level programming concepts.
Figure 1-2: Relevant courses in the current MIT EECS curriculum, which was intro-
duced in 2022. Notably, 6.190 was inserted to be taken between 6.100A and 6.191.
As previously noted, there were two courses in the old curriculum that did cover
low-level programming: Interconnected Embedded Systems (6.08, now 6.901), which
23
covered C, and Computation Structures (6.004, now 6.191), which covered assembly
language. Due to the curriculum change, 6.901 is not currently being offered at MIT,
and 6.191 no longer teaches assembly language, instead expecting that students learn
RISC-V assembly language prior, while taking 6.190. As a result, some of our course
materials are adapted from 6.901’s coverage of C and 6.191’s coverage of RISC-V
assembly language.
In 6.901, students learned C primarily as a tool to program an Arduino-based
microcontroller. However, there were various compatible external libraries that stu-
dents were allowed to use to control the peripherals on the microcontroller. These
library functions abstract away key elements of low-level programming and hardware-
software interactions, such as bit manipulation and direct memory accesses. 6.190
places more of a focus on low-level programming, such as interpreting and interacting
with individual bits stored in memory.
The overall treatment of RISC-V assembly in 6.190 is largely similar to that in
6.191. However, since students have familiarity with low-level programming in C from
the first portion of the course, we can assume exposure to ideas such as pointers and
bit manipulation. This allows us to focus more on other key concepts, such as how
RISC-V instructions are represented and organized in memory. We also developed
new educational tools, such as a more convenient and user-friendly RISC-V simulator,
to help students better understand how registers and memory evolve as different types
of RISC-V assembly instructions are executed.
24
Chapter 2
Course Content
25
data structures in modern computational systems. Studies assembly lan-
guage to facilitate a firm understanding of how high-level languages are
translated to machine-level instructions” [3].
6.100A is the introductory Python course offered at MIT that students are required
to take, or otherwise show proficiency with its material, before taking this class.
2.1 C
We use the C programming language as the primary tool to understand and interact
with memory because it is more similar to higher level languages, such as Python,
than assembly language is. We use The C Programming Language, Second Edition by
Brian Kernighan and Dennis Ritchie [4] as the textbook and C reference for the course.
Because students are expected to have been exposed to programming in Python, the
course does not introduce or focus on topics such as variables, arithmetic and logical
computation, and control flow. Instead, it initially highlights where Python and
C differ, primarily syntactically, then it shifts focus to new concepts such as how
program variables and their contents reside in memory.
For example, a student entering this class should be able to understand the fol-
lowing line of Python code: x = 5. This is simply assigning the variable x to hold
the value 5. In C, an equivalent line of code would be int x = 5;. This idea of vari-
able assignment should be familiar to a student with a Python background, however,
this course further enhances their understanding of variable assignment by explaining
that:
• An int is a 32-bit (on the machine used in our class and in 6.191) integer value.
So, 32 bits in memory will be dedicated to holding this variable, x.
• One byte corresponds to 8 bits. So, an int, which is a 32-bit value, is 4 bytes.
26
• Memory is made up of bits, which can only hold the value 0 or 1. So, numerical
values stored in memory are encoded in base-2, or binary, and the base-10 value 5
will be stored in memory as the base-2 value 00000000000000000000000000000101.
This example highlights one key topic in this class – how information is represented
in memory. We introduce various information encoding schemes, including binary,
hexadecimal, and ASCII so students can become familiar with this idea.
Another area of focus in this class is how a programmer can directly interact
with and modify memory, which is a new concept for the average student taking
this course. The course discusses bitwise operations to expose students to the idea
of manipulating individual bits in memory, and it introduces pointers so students
can gain confidence with using addresses to access particular values in memory. The
laboratory component of this class especially reinforces these concepts, with each
laboratory assignment requiring some form of bit manipulation. This also allows
students to understand how more complex data structures, specifically arrays, are
represented in memory. The course also explores how pointers enable software control
of physical hardware via memory-mapped input and output.
27
instructions, or any other instructions that interact with the operating system. We
also introduce a set of relevant RISC-V pseudoinstructions, while emphasizing the
fact that psuedoinstructions must be translated into equivalent RISC-V instructions
before being assembled into the 32-bit binary encodings understood and executed by
a processor.
Students first gain familiarity with the RISC-V ISA and its different instruction
types, then they spend time compiling C programs into equivalent RISC-V assem-
bly programs. Although this translation from C to assembly language is a process
normally done by a software compiler, it is important for students to understand it,
as it can inform how they write higher-level programs and provide the background
necessary to explore compilers in future coursework. As this course places a strong
emphasis on memory, students also learn how RISC-V assembly instructions are en-
coded into binary values that are stored in a particular section of memory. This
course focuses on the 32-bit instruction encoding of RISC-V instructions because this
class, along with MIT’s 6.191, uses a 32-bit machine.
Due to the limited amount of time available to introduce material in the class,
6.190 does not introduce compiler design, in favor of focusing on more memory-related
topics. If this class were to be extended to be offered over a full semester, this topic
would be given priority above the other topics we could introduce, as it would give
students the more complete picture of how high level languages are translated into
binary machine code. Currently, in 6.190, students act as the compilers, manually
translating C programs into assembly language instructions and the corresponding bi-
nary, gaining the background knowledge necessary for learning about compiler design
in future coursework.
Assembly language also allows students to directly interact with the stack, and
they must use the stack in order to adhere to RISC-V calling convention. This is
something that is not explicitly necessary when programming in C or a higher-level
language, as it is generally handled when those higher-level programs are compiled
into assembly language and machine code. Learning about RISC-V calling conven-
tions and stack discipline gives students a deeper understanding of how the stack
28
section of memory is structured and how it evolves throughout a program.
Given the goals we established for this class, we identified the topics that we wanted
it to cover. This course was designed with the intention to offer it during a half of a
given semester at MIT, so, constrained by MIT’s academic calendar, we distributed
topics to create six weeks’ worth of material as shown in Table 2.1.
Week 1 C Syntax
Data types in C
Unsigned binary encoding
Hexadecimal encoding
Bitwise operations
Pointers in C
Week 2 Signed (two’s complement) binary encoding
ASCII character encoding
Arrays in C
Pointer arithmetic in C
Week 3 Strings in C
Structures in C
Week 4 RISC-V assembly language
RISC-V instruction encoding
Instruction memory
Week 5 RISC-V calling convention
Stacks
Week 6 Memory layout
Heap
Dynamic memory allocation
Table 2.1: The topics covered in 6.190 are distributed over six weeks, because the
course is offered during half of an academic semester at MIT.
A typical week in 6.190 includes three class meetings: a lecture, a recitation and
a laboratory section, and three assignments: a lab, a post-lab, and a set of exercises.
There is one exam administered at the end of the class.
The week’s material is first introduced during lecture, then it is reinforced through
examples and practice problems in recitation. Students will then attend a labora-
29
tory section where they explore the week’s concepts via developing firmware for an
embedded system based on a RISC-V microcontroller. Weekly assignments include
completing the lab from class, as well as completing a related “postlab” and a problem
set consisting of various exercises.
2.3.1 Lecture
Each week includes a 90-minute lecture where the week’s material is first introduced
by an instructor of the class. The lectures are primarily presentation-based, but
certain weeks feature live coding demos. The lecture meeting is scheduled at the
beginning of a given week, so students can spend the rest of the week interacting
with the material via the other components of the class.
2.3.2 Recitation
After lecture, students attend a 90-minute recitation section led by a course teaching
assistant. Recitations focus on guiding students through solving problems using the
concepts introduced in lecture.
2.3.3 Exercises
Each week, students are assigned a set of exercises to complete. These exercises range
from simple examples, such as converting the binary representation of a number to
its decimal representation, to more complex problems, such as writing nontrivial C
functions or assembly procedures. Some exercises have been adapted from those used
in 6.191 and 6.901, and some are new, original exercises developed specifically for this
class.
There are two types of laboratory-based assignments, labs and postlabs, where stu-
dents explore a week’s concepts by developing firmware for an embedded system based
30
on a RISC-V microcontroller. The labs are attempted during a weekly 2.5-hour in-
class session, so they are designed to take students that amount of time, on average,
to complete. Postlabs are often continuations or extensions of that week’s lab, so they
are designed to be completed independently outside of class after completion of the
lab. Labs and postlabs both include short check-in conversations with a staff mem-
ber to verify system functionality and student understanding of their implementation
and related course topics. The development of the lab and postlab assignments is a
central focus of this thesis, and it is discussed in depth in Chapter 3.
There is an exam during the sixth week of classes that assesses students’ understand-
ing of topics covered during the first five weeks of the course. Because the topics
at the core of the course are emphasized and applied during the labs and postlabs,
exams include questions related to them. Topics, including memory layout, the heap,
and dynamic memory allocation, introduced during Week 6 are not included on the
final exam due to Institute regulations surrounding scheduling exams at the end of
the term.
31
32
Chapter 3
Laboratory Component
The lab and postlab assignments rely on programming a physical embedded system,
so we needed to identify and design software and hardware tools to support embed-
ded systems development. Specifically, we needed to design an embedded system
and select useful software development tools. While doing this, we considered im-
portant factors including availability, cost, user experience, and educational benefits.
Availability and cost are critical because we must obtain sufficient quantities of any
33
necessary items in a timely manner so that hundreds1 of students at a given time
have the necessary tools to complete the assignments. User experience is a significant
factor because students completing these assignments are not expected to have any
relevant background beyond basic Python programming exposure. Ideally, the tools
would be simple to use so that they don’t distract students from the educational
goals of the assignments. Lastly, this system is being used as an educational tool in
a classroom environment, so some components were selected based on how well they
could facilitate understanding of the course material.
3.1.1 Hardware
Figure 3-1: A 6.190 lab kit containing a RISC-V development board, course-specific
printed circuit board, and an LED display.
Each student taking 6.190 is given a physical kit that they are required to use
while completing labs and postlabs throughout the class. This kit consists of three
main hardware components: a development board based on a RISC-V microcontroller,
a printed circuit board (PCB) containing buttons and switches, and an 8x32 LED
1
Based on historical enrollment in this class and in 6.191, we anticipate that enrollment in future
offerings of 6.190 may reach upwards of 300 students per offering.
34
display. It also includes a wiring kit and breadboards to connect these components,
as well as a cable and corresponding adapter to connect the microcontroller to their
computer to power the development board and program the microcontroller. A lab
kit, as it would be provided to students at the beginning of the class, is shown in
Figure 3-1.
The focal component of the lab kit is the development board containing the RISC-
V microcontroller that the students will program. We initially developed the labs
and postlabs around the SparkFun RED-V Thing Plus development board, which
features a SiFive FE310 system-on-a-chip with a RISC-V processor. However, during
development, we encountered an intermittent problem with uploading programs to
the board, so we decided to base the assignments on the Espressif Systems ESP32-
C3-DevKitM-1 development board. This development board includes an Espressif
Systems ESP32-C3-MINI-1 module with an ESP32-C3 RISC-V microprocessor. One
additional advantage of the Espressif board is the cost: on Digi-Key Electronics,
as of May 2023, the Espressif development board cost $8.00,2 while the Sparkfun
development board cost $32.50.3 At scale, when hundreds of students are taking the
class at a given time, the $24.50 price difference per individual board can result in a
significant reduction in cost.
Most of the designs the students are targeting in these assignments require user
input in the form of pressing a button or toggling a switch. In total, the assignments
require eight switches and six buttons. We designed a printed circuit board containing
these components so they are positioned in a uniform arrangement convenient for
interacting with the systems developed for the assignments. Students can easily
connect the buttons and switches to the microcontroller using just one wire for each
input and one wire for ground, reducing the amount of time and thought required to
set up the hardware system for each lab. The circuit is designed so that when a switch
2
Espressif ESP32-C3-DEVKITM-1 Development Board (Digikey): https://www.digikey.com/
en/products/detail/espressif-systems/ESP32-C3-DEVKITM-1/13684315. Price reference in
text accurate as of April 29, 2023.
3
Sparkfun DEV-15799 Development Board (Digikey): https://www.digikey.com/en/
products/detail/sparkfun-electronics/DEV-15799/10715591. Price referenced in text accu-
rate as of April 29, 2023.
35
is in the down position or a button is pressed, a connection can form between the
corresponding input pin and ground, resulting in a digital low value on the pin. When
a switch is in the up position or a button is not being pressed, the corresponding input
pin would be disconnected from ground and an internal pull-up resistor would make
the pin input have a digital high value.
Figure 3-2: The printed circuit board (PCB), containing six buttons and eight
switches, designed specifically for use in the 6.190 embedded system.
36
to represent an array with eight unsigned 32-bit integer elements. Each element in
the array corresponds with a row on the display, and each bit in a given element
corresponds with a pixel in that row.
We strategically chose the origin point, or orientation, of the display so that the
LEDs would directly match the contents of memory. The first element of the array is
be rendered in the top row of the display, and each row is a direct representation of
the binary encoding of the corresponding value in the array – the leftmost LED in a
given row corresponds to the most significant bit, and the rightmost LED in a given
row corresponds with the least significant bit. For example, putting this together, if
a student modifies the least significant bit of the first array element, the rightmost
pixel in the top row of the display would change. Figure 3-3 provides an example of
how the contents of a one-dimensional array in memory correspond to the contents
of the LED display.
Figure 3-3: The contents of a software screen buffer array directly correspond to the
contents of the 8x32 LED display included in the 6.190 lab kit.
We provide students with the SPI interface that transmits data from an array in
memory to the LEDs on the display. We chose to implement this interface purely
using software, rather than using the hardware SPI peripheral available on the mi-
crocontroller. This software SPI interface is educationally beneficial for two main
reasons: it is primarily driven by functions that are ultimately implemented by stu-
dents during the labs, and the interface can be understood by the students taking the
37
class.
3.1.2 Software
Students are instructed to use the PlatformIO Visual Studio Code extension to com-
plete the lab and postlab assignments. We chose to use PlatformIO for this class
because it is simple for beginners to learn and use, supports a myriad of microcon-
troller hardware platforms, and is compatible with all of the major computer operating
systems (Windows, MacOS, and Linux). Students are instructed to use Visual Studio
Code as their code editor due to its integration with PlatformIO.
PlatformIO is also available to be used as a command-line interface (CLI), which
would remove the dependency on Visual Studio Code, but we decided against using
it. We do not expect students to have any familiarity with using the command line
and, because this class is only six weeks long, it would be suboptimal for the tools
to have a learning curve that would distract students from the course material. If
the prerequisite course, 6.100A, were to explore using the command line and related
tools, this option would become more compelling, as it would not require as much
overhead to become familiar with the tools.
There are two types of laboratory-based assignments: labs and postlabs. They are
designed to be completed sequentially, with the lab coming before the postlab. Stu-
dents are provided with detailed instructions and starter code (see Appendix A), that
includes any code necessary for the system’s functionality but will not be implemented
by students due to time constraints, for each assignment.
The first lab provides an overview of the lab kit and PlatformIO, then it guides
students through writing C functions that interact with the general purpose input and
38
output (GPIO) pins on the ESP32-C3 microcontroller. Students are given a header
file (see Appendix A.1.2) with declarations of board-specific parameters and functions
that are used extensively in future labs and postlabs. Some of these functions are
already implemented for students, and the rest of them are implemented by students
during this lab.
Conceptually, this lab connects to the central theme of memory because it focuses
on writing low-level software to interact with individual bits in memory using C. This
requires the use of bitwise operations and pointers. The lab first introduces students
to three main bit operations: setting bits, clearing bits, and reading bits. Then,
students apply these operations to three GPIO-related functions:
• pinRead, a function to read a digital value from an input pin on the ESP32-C3
These functions are used for the same purposes as Arduino’s pinMode [6], digitalWrite
[7], and digitalRead [8], respectively. Oftentimes, hobbyists or beginner-level stu-
dents taking classes such as MIT’s 6.901 would have used these functions without
needing to understand the details of their implementations. However, this class re-
quires them to actually implement them and think about the underlying memory
structure.
For example, the ESP32-C3 microcontroller has a designated memory address,
0x6000403C, that holds a 32-bit value where each bit corresponds to the digital value
of a GPIO input pin [9]. For a program to obtain the value of a GPIO input pin, it
must first access the contents of that memory address, then extract the individual bit
that corresponds with the input pin of interest. This requires the use of pointers, bit
shifting, and bitwise logical operations, which are key topics central to the first week
of 6.190.
Students develop each individual function on the class website, using checkers that
run the their code submissions against various test cases to verify functionality and
39
assign a grade accordingly. Once a student’s code passes the online test cases, they can
transfer it locally to Visual Studio Code, then use PlatformIO to compile the project
and upload the resulting binary to the development board. They further verify the
behavior of the GPIO output pins by connecting two LEDs to two output pins on
the microcontroller, and they use a button input and serial monitor output to verify
the functionality of the input pins. Then, they expand their program so that they
use logical operations based on two button inputs to control the two LED outputs –
for example, they are instructed to make one LED consistent with the output of an
exclusive-or function between the two button inputs. This exercise is included with
the lab to require students to reason about logical and bitwise operations in C.
In Postlab 1, students build a game named “Hit the Bit,” which is based on an early
computer game called “Kill the Bit.” For this game, the system uses eight switch
inputs and eight adjacent LEDs on the LED display. The game starts with one
illuminated LED that continuously shifts to the right until it reaches the rightmost
column, then it wraps around to the leftmost column and continues to move to the
right. This behavior, independent of the game logic, is displayed in Figure 3-4.
Each switch maps to one of the eight LEDs, and on each step, the state of each
switch is compared to the state of each LED on the display. A switch in the up
position matches an illuminated LED, and a switch in the down position matches an
LED that is turned off. If a switch does not match its corresponding LED, then that
LED should be illuminated on the next step. Otherwise, that LED should become
or stay turned off. The player’s objective is to turn off all of the LEDs by flipping
the switches so that they match the state of the eight LEDs. Figure 3-5 provides an
example of typical gameplay.
Students implement three main components of the game. First, they implement
the right-shifting and wrapping behavior. The LEDs are represented by an unsigned
eight-bit integer variable. Next, they write a function to store the states of the eight
switches in another unsigned eight-bit integer variable, which also requires the use of
40
Figure 3-4: Postlab 1 initial right shifting behavior independent of switch inputs and
game logic. The state of the rightmost column wraps around to the leftmost one on
each step.
their pinRead function from Lab 1 and bit shifting. Last, they write a line of code to
compare the state of the eight LEDs to the state of the eight switches. Throughout
these exercises, students are required to write C code that uses bit shifting and bitwise
operations to achieve a working game.
In Lab 2, students create a system that displays messages on an LED display, as shown
in Figure 3-6. While developing this system, students are introduced to higher-level,
relevant ideas such as ASCII encoding, null-terminated strings, state, and character
rendering while still being required to interact with individual bits in memory. Stu-
dents also gain familiarity with arrays – this lab is the first where students use an
array in memory to represent the contents of the 8x32 LED display, and the message
ultimately rendered on the LED display is initially stored in a character array.
Students first write a function to read the input pins connected to seven switches
41
Figure 3-5: Example of typical Postlab 1 gameplay. Two switches, SW6 and SW1,
are in the incorrect positions, while the rest are correct. As a result, the LED corre-
sponding with SW6 will turn on, and the LED corresponding with SW1 will stay on.
The rest of the LEDs will become or remain turned off. Then, all of the LEDs will
shift to the right, and the rightmost LED will wrap around to the leftmost column.
and interpret them as an eight-bit integer, with the most significant bit set to 0.
Switch 0 (SW0) on the printed circuit board (PCB) corresponds to the least significant
bit in the value, and switch 6 (SW6) corresponds to the most significant bit, as
displayed in Figure 3-7. This orientation was chosen because it matches that of
a binary number, making each switch input equivalent to the corresponding bit in
an eight-bit value. This function should use bit shifting and bit masking, requiring
students to recall core concepts from the previous week. The eight-bit value returned
from this function is then interpreted as an ASCII character when it is printed on the
serial monitor.
Students then add a button input to their system so that the switch inputs are
only sampled once upon a button press. After verifying that the system correctly
prints a single character upon a button press, students are instructed to write a
program that uses the switches and button inputs to fill an array of characters with a
null-terminated string. A character should be added to the string upon each button
42
Figure 3-6: Lab 2 system displaying the message “RISC” on the 8x32 LED array.
press, and the string should be printed to the serial monitor when the null-terminating
character is entered. Once the string has been printed, the array should be reset so
that the same section of memory can be used again to store another string. This
exercise requires students to work with arrays using C, and it helps them to become
familiar with how strings are represented in memory.
The remainder of the lab focuses on rendering these messages on the system’s LED
display. First, students are introduced to an adapted header file, named ascii.h, con-
taining an 8x8 bitmap for each character [10]. The file consists of a two-dimensional
array with one entry for each of the 128 ASCII characters. Each entry is an array
containing eight unsigned eight bit integers – each integer maps to a row, and each
bit indicates whether or not a pixel in that row should be illuminated, as displayed
in Figure 3-8.
Students must write a C function that, for up to the first four characters in a
given string, places the contents of the 8x8 bitmap from the header file into the
screen buffer array so that,when the data in the array is transmitted to the LED
43
Figure 3-7: In Lab 2, switch inputs on the lab kit are interpreted as eight-bit binary
numbers and ASCII characters.
display, the message will correctly appear on it. This requires students to interact
with memory by accessing or modifying both entire array elements and the individual
bits in those elements.
Due to the unconventional way we chose to orient the LED display, the bitmaps
from the original ASCII header file resulted in the letters appearing to be flipped
horizontally on our display, as shown in Figure 3-9. During the first three offerings of
the course, students were required to correct this using a provided helper function to
reverse the bits in each value they used from the ASCII header file before placing it in
the screen buffer. This ended up being a large source of confusion for students, and
it distracted them from the intended purpose of the lab, so in more recent iterations
of the class, we provided students with an updated ASCII header file where the bits
in each array entry are oriented so that students can use the values directly, as shown
in 3-9.
For the second postlab, students develop a system that displays a scrolling message
on the 8x32 LED display. The rest of the system is fairly similar to that from Lab
2, where students wrote a program that would statically display a message, except,
rather than requiring students to enter the message through switches and button
presses, messages are hard-coded in the program. The scrolling effect is created by
44
Figure 3-8: An example of rendering an 8x8 character on the LED display using a
provided bitmap.
moving the message on the LED display one spot to the left periodically, as shown in
Figure 3-10. In the lab, we update the display approximately every 100 milliseconds
to create a smooth scrolling effect.
For this assignment, students first write a function that, given a null-terminated
C string, calculates the number of characters in the message. This is similar to the
C library function strlen [4]. The program then uses the calculated length of the
string to determine the number of images, or frames, that must be rendered in total
to display the entire message – each successive frame effectively replaces the leftmost
column in the image by shifting every column to the left and filling the rightmost
column with new data, so this can also be thought of as the number of columns
required to render the entire message. The starter code for the lab then iterates
through each column in the message, showing the relevant portion of the message on
the LED display on each iteration.
The majority of the postlab involves writing a function that, given an array con-
taining a null-terminated string and the number of loop iterations that have occurred
since the beginning of the message, displays the correct portion of the message on
45
Figure 3-9: Using the values from the original ASCII header file directly resulted
in letters appearing flipped on our LED display. The values needed to be reversed
in order for the letter to appear correctly. Students originally had to use a helper
function in their program to reverse the bits in each value they used from the file, but
in later iterations of the course we provided them with an updated ascii.h file with
the entries flipped for them.
the LED display. In Lab 2, the LED display rendered four 8x8 characters, which
were aligned in the 32-bit wide screen buffer. In this assignment, these characters
may not be perfectly aligned, so portions of the leftmost and rightmost character may
be displayed. This function must first determine which characters, as well as what
portions of those characters, to render. Then, it must obtain the bitmap for each
of those characters from the ASCII header file used in Lab 2 and set the bits in the
screen buffer accordingly, with the new consideration that only portions of the first
and last character may be displayed. This assignment is intended to give students
more exposure to arrays in C, as well as to manipulating individual bits in memory
through their interactions with the screen buffer.
46
Figure 3-10: A scrolling effect can be created on the LED display by periodically
shifting the contents of the display. Above, the message. "Hi, there!" begins to scroll
across the display.
During Lab 3 and Postlab 3, students implement a Snake game, where the objective
is to grow a continuously-moving snake as large as possible without allowing it to
collide with itself. A player navigates the snake around the board in search of food,
and, when the snake encounters food, it eats it and grows. The game ends when
the snake’s head collides with its body. Some game variations also end the game
when the snake collides with the edge of the game board, but our implementation
instead has the snake wrap around to the other side of the board when it encounters
47
an edge. This lab requires five button inputs: four for navigation and one for game
reset. Additionally, the LED display is used to render the game board.
The lab opens with a discussion of how to represent a given location on the game
board. We use the 8x32 LED display as our game board, so the representation must
be able to encode at least 256 unique location values. We initially considered repre-
senting each location with two unsigned eight bit integers, one for the x-coordinate
and one for the y-coordinate. The primary benefit of this representation is that it’s
straightforward and intuitive; many students would have likely chosen this approach
if not given any guidance. We ultimately decided to represent each location as a
single unsigned eight-bit integer, where the x-coordinate is encoded in the upper five
bits and the y-coordinate is encoded in the lower three bits, as shown in Figure 3-11.
This approach requires half of the memory than the original, and it employs a more
interesting and educational encoding scheme that requires students to use bitwise
operations and shifting to extract and modify individual bits in the variable. Lastly,
there is a modulus built into this encoding, since there are not any extra, unused bits.
This results in the game having the desired “wrap-around” behavior without needing
to explicitly implement it.
Students first implement four helper functions to extract information from vari-
ables representing locations:
These functions require the use of operations that modify or extract bits stored in
memory, which strongly reinforces key concepts that have been introduced during the
first three weeks of the course.
The following section of the lab involves rendering the game board on the LED
display. Each of the objects in the Snake game is associated with an unsigned eight-
48
Figure 3-11: In Lab 3 and Postlab 3, locations on the 8x32 LED display are repre-
sented using a single unsigned eight-bit variable, with the upper five bits encoding
the column and the lower three bits encoding the row.
bit value indicating its location on the board, using the representation previously
discussed. The program uses these locations to set the corresponding bits in the
software screen buffer, which is then transmitted to the LED display. Students are
first instructed to write a function named setPixel that sets a bit in the software
screen buffer given a location and a binary value indicating whether or not the LED at
that location should be on. This function is conceptually relevant because it requires
the manipulation of individual bits in the screen buffer array.
Structures in C are introduced during the third week of the class, so the snake used
in the game is represented using a C structure containing three members: an array
representing the snake’s body, the direction of the snake’s movement, and the length of
the snake. The elements of the body array are unsigned eight-bit integers representing
the locations on the game board occupied by the snake. The first element in this array
is the front of the snake, or its head, and the number of array elements considered
when rendering the snake is dictated by the length member of the structure. The
other object in the game is the food eaten by the snake, and the program keeps track
49
of its location in an unsigned eight-bit integer variable. Students are instructed to
write a function, drawBoard, that renders both the snake and the food on the game
board using the setPixel function they had previously implemented.
The program must then be updated so that the player can use button presses to
change the snake’s direction of travel. Then, students implement a function named
updateSnake that moves the snake one step in its current direction of travel. This
function requires students to update the Snake structure by using a pointer to it,
which is a topic introduced in the third week of the class. The final task in the lab
portion of the Snake project is implementing a function to randomly generate food
for the snake. After this lab is completed, the system is in a state where a player
can interact with a continuously-moving snake, but the game logic has yet to be
implemented.
In Postlab 3, students complete their Snake game by implementing the game logic.
First, the game must be able to detect when the snake has eaten food, which occurs
when the snake runs into it. Students implement a function, snakeAteFood, that,
given the locations of the snake’s body and food, returns whether or not the snake’s
head is occupying the same location on the game board as the food. The game
must also be able to identify when the snake collides with itself, resulting in a loss.
Students implement a function, snakeCollisionCheck, that, given a pointer to a
Snake structure, returns whether or not the snake’s head has collided with its body.
After these functions have been implemented, the system works like a conventional
Snake game.
Students must update their program so that the game will restart if the user
presses a reset button after the previous game has ended. Lastly, because this is
the final lab that focuses on C, students are given the opportunity to implement one
open-ended enhancement to their snake game. These enhancements include:
50
• Adding a pause button to the game.
• Having the snake move faster every time it consumes food, and having the snake
slow down each time it moves without eating food. The game ends if the snake
slows down too much.
• Keeping track of the score throughout the game and reporting it at the end.
• The snake should visibly decay until it’s no longer visible when the game ends.
Students are not provided any written guidance for any of these enhancements so
they can independently reason about how they should design and implement them.
The fourth lab is the first that focuses on RISC-V assembly language, and it is specif-
ically designed to help students become comfortable translating C programs into
assembly language programs so they can better understand the exact operations car-
ried out by the processor during program execution. Students are given an assembly
file (shown in Appendix A.6.2), where they will put their RISC-V assembly imple-
mentations of various C functions. This file will be used along with the existing 6.190
C header file throughout the remaining lab and postlab assignments.
First, students translate some of the C functions written during Lab 1 into RISC-
V assembly language. The lab instructs students to translate pinRead first because
its RISC-V assembly implementation does not require any control flow instructions
beyond the provided return instruction. This allows students to become comfortable
with using computational instructions before moving on to implement more complex
procedures. Then, students implement pinWrite and pinSetup, which do require the
use of control flow instructions to implement conditional statements. All of these ex-
ercises require students to use RISC-V assembly instructions to interact with memory
and perform computation on registers. Students verify functionality of these functions
in the same way they did during Lab 1, using two LEDs connected to two output pins
and printing messages to the serial monitor in response to the button input. Students
51
also implement a function extremely similar to their setPixel C function from Lab
3 in RISC-V assembly, as future assignments will involve setting bits in the software
screen buffer.
Writing programs in RISC-V assembly language can be a large jump from writing
programs using higher-level languages, including C, so we looked for opportunities to
simplify other aspects of the lab. In particular, we chose to have students translate C
functions they had previously written, rather than introducing a new application re-
quiring new C functions. Students should already be familiar with these functions, so
they can focus on how to translate them into assembly, rather than first needing to fa-
miliarize themselves with new C functions. Additionally, these functions in particular
allow students to review concepts core to the class, such as bit manipulation.
The fourth week of the class, during which Lab 4 and Postlab 4 are assigned,
does not cover some critical elements of assembly language, including procedure calls,
RISC-V calling conventions, the stack, or the specific purpose of each RISC-V register.
This both limits the types of procedures that students can write and requires the lab
instructions to provide strategic guidance to students. First, none of the procedures
that students are instructed to implement in this lab call other assembly procedures,
so, for every procedure, a correct implementation that does not require any additional
instructions for adherence to RISC-V calling conventions exists. However, we must
impose a constraint on the registers that students may use in order to ensure that their
implementations will follow RISC-V calling conventions without the use of additional
instructions.
We also limit the set of registers that students may use in their implementations
to register x0 and registers a0 through a7. We allow the use of x0 because its value is
zero, which is convenient for assembly language programs, and it is immutable, mean-
ing that no procedure needs to be concerned with about its value being overwritten
by another procedure. We chose to use registers a0 through a7 for two main reasons.
First, they are caller-saved registers, meaning that a procedure is not responsible for
maintaining their values unless it is making procedure calls itself. None of of the
procedures in this assignment call any other procedures, they do not need to include
52
additional instructions to adhere to RISC-V calling conventions. Additionally, stu-
dents will have needed to use these registers as function arguments and return values
anyways.
Although the assembly procedures implemented by students in this lab do not call
procedures, they are called by other functions. We cannot ignore some aspects of these
interactions, even though students have not yet been introduced to them. First, each
procedure implementation will need to use specific registers as function arguments
and return values. Students have not yet been introduced to the specific purposes
of registers, so the lab instructions tell students which register holds each function
argument at the start of the procedure and which register to place the function’s
return value in before the procedure ends. Furthermore, each assembly language
procedure requires a dedicated instruction to return control back to the procedure
that called it. This instruction is included in the lab starter code at the end of every
procedure.
Additionally, during the second quarter of the spring 2023 semester, we banned
students from using RISC-V pseudoinstructions during the fourth week of the course,
which includes Lab 4 and Postlab 4. This requires students to write assembly code
using just operations that are directly carried out by a RISC-V processor.
The fourth postlab is adapted from part of an MIT 6.191 assignment, where students
translate a Python or C implementation of the bubble sort algorithm into RISC-V
assembly. For this class, we removed the reference Python implementation of the
algorithm because students taking this course should be comfortable with reading C
programs by the time they are given this assignment. Additionally, students will add
code to their bubble sort implementation that will render the algorithm on the LED
display. However, after students implement the bubble sort algorithm, they will need
to add a procedure call to invoke a C function that renders an eight-element array on
the LED display. Students must strategically place this procedure call within their
implementation so that the display updates every time a swap takes place. After
53
completion of the postlab, the system displays the bubble sort algorithm repeatedly
on the LED display.
Just as with Lab 4, this postlab is assigned before students are familiar with topics
related to assembly procedure calls, so students are provided with the same guidance
regarding these topics and their implementations are subject to the same register
usage constraints. We also provide students with the code necessary to make the
single procedure call that they must add to their bubble sort algorithm to render the
array on the LED display. This code includes both the procedure call itself, as well as
the instructions necessary to adhere to RISC-V calling conventions, so that student
implementations will still work as expected after the addition of the procedure call.
Additionally, during the second half of the spring 2023 semester, students were not
permitted to use pseudoinstructions while completing this assignment.
The postlab also includes an exercise from the same 6.191 lab where students
explore the disassembly of an assembly procedure, particularly focusing on the bi-
nary encoding of RISC-V instructions. We especially prioritized keeping this exercise
because this class aims to emphasize how RISC-V instructions are represented in
memory in addition to how RISC-V instructions can be used.
The fifth lab guides students through implementing Conway’s Game of Life using
RISC-V assembly language, placing a particular focus on RISC-V procedure calls.
In particular, it reinforces concepts pertaining to control transfer among procedures,
RISC-V calling conventions, the stack, and the purposes of the different RISC-V
registers. In this lab and the following postlab, students may use all RISC-V registers,
and they are responsible for ensuring that their procedures all adhere to RISC-V
calling convention. Students completing this lab during the second quarter of the
spring 2023 semester, who were not permitted to use pseudoinstructions to complete
the previous week’s assignments, are allowed to use them during this lab and Postlab
5. Students completing this lab during the other offerings of the course were allowed
to use psuedoinstructions for all assignments, including this lab.
54
The lab first introduces students to the rules of the game [2]:
1. A living cell with less than two living neighbors dies due to underpopulation.
3. A living cell with more than three living neighbors dies due to overpopulation.
4. A dead cell with three living neighbors becomes a living cell due to reproduction.
3. All other living cells die, and all other dead cells remain dead.
The reduced set of rules is used for the lab’s implementation of the game. These rules
are applied repeatedly to a game board full of cells, as shown in Figure 3-12.
Figure 3-12: The Game of Life rules [2], applied four times to two initial configurations
of an 8x8 game board. The Loaf configuration does not change in response to the
rules, while the Glider configuration does.
The assignment uses the lab kit’s LED display to visualize the game board. A
living cell will correspond to an illuminated LED, and a dead cell will correspond
55
to an LED being off. Due to the LED display only having eight rows and thirty-
two columns, which is much smaller than the size of a typical game board used
for the Game of Life, the lab implementation will allow cells to “wrap around” the
game board. That is, a cell in the top row of the board will have neighbors in the
corresponding columns of the bottom row, and a cell in the leftmost column of the
board will have neighbors in the corresponding columns of the rightmost row.
At a high level, the Game of Life can be implemented using an infinite loop that
calls a function that updates the board on each iteration. Students are provided with
a C implementation of this update function, and they must translate it into RISC-V
assembly. This update function loops through every cell on the game board, each of
which maps to a pixel on the LED display, and:
• Determines the new status of the cell accordingly, using the rules of the game.
Students are provided with a helper procedure named getPixel that returns the
status of a cell given its location (in x and y coordinates) and a pointer to the game
board array.
The update function uses a helper function named checkNeighbors that, given
a pointer to the game board and a set of coordinates, returns how many of the
corresponding cell’s neighbors are alive. Students must implement this function using
RISC-V assembly language. One key aspect of this checkNeighbors procedure is that
it uses a helper procedure named tallyNeighbors to actually count and return the
number of living neighbors that the given cell has. So, students must pay special
attention to make sure they adhere to RISC-V calling conventions, which is a central
theme of this week’s content, when implementing this procedure and using the helper
procedure.
After successfully implementing the checkNeighbors procedure using RISC-V as-
sembly, students then complete an exercise pertaining to the tallyNeighbors helper
56
function used in their implementation. For this exercise, students are provided with
a complete RISC-V implementation of tallyNeighbors, that will work as expected
once it adheres to RISC-V calling convention. Students must add instructions to the
procedure to make it follow calling conventions. The instructions they may add are
limited to those that increment or decrement the stack pointer, push data onto the
stack, or pop data from the stack. We only require students to handle the calling
convention portion of this procedure so that they can focus on understanding those
concepts in particular.
The final portion of this lab requires students to translate the board update func-
tion from C to assembly language, making sure to follow RISC-V calling conventions
where applicable. After students complete this, their system will be able to run the
Game of Life. Students are provided with a couple of initial game configurations and
are encouraged to create their own.
The fifth postlab assignment is another adaptation of an existing MIT 6.191 assign-
ment. It is similar to Postlab 4, however, the sorting algorithm to be translated
to RISC-V assembly is quicksort rather than bubble sort. We modified the assign-
ment from the 6.191 version so that students implement and test the partitioning
procedure separately from the main quicksort procedure that calls it. Implementing
quicksort in assembly requires strategic use of the stack to make the procedure adhere
to calling convention. Similar to Postlab 4, the lab instructs students to call an array
visualization procedure from their quicksort procedure so that they can visualize the
sorting algorithm on their LED display. However, in this assignment, students are not
given the code to call this procedure, and instead must write it themselves, because
procedure calls and calling conventions are a central focus of this week’s material.
57
58
Chapter 4
The low-level nature of assembly language programs makes it difficult to gain an un-
derstanding of their general behavior by just reading them. This results in assembly
language programs often being difficult to debug when they don’t work as expected,
requiring a programmer to walk step-by-step through each of the individual instruc-
tions to find the root cause of the unexpected behavior. While it is possible for a
programmer to manually keep track of the state of registers and memory as they step
through the program, that approach is tedious, inconvenient, and prone to error.
All exercises and assignments in 6.190 are submitted via CAT-SOOP [11] [12]
checker questions embedded in the course website. CAT-SOOP has many question
types used to verify different types of submissions, from numerical answers to pro-
grams [12]. While preparing for the preliminary offering of the course, we had created
a RISC-V assembly program checker question type that uses a derivation of RiscEmu,
a RISC-V emulator written in Python [13], to run assembly programs submitted by
students against sets of test cases.1 This chapter discusses this extension of this RISC-
V assembly program checker to include a convenient, user-friendly debugging tool. In
addition to helping students debug their assembly programs, this tool can enhance
their understanding of how individual RISC-V instructions interact with registers and
memory.
1
Future mentions of RiscEmu refers to our derivation of the package, rather than the package
from the original source [13]. The derivation can be found at this link: https://github.com/
jodalyst/riscemu
59
4.1 Features and Typical Use
The debugger is integrated with the existing RISC-V assembly program checker ques-
tions on the course website, and it is activated when a student submits a program
including at least one breakpoint instruction. This breakpoint instruction, named
csbreak (for CAT-SOOP-Breakpoint), is unique to this class and is not in the RV32I
instruction set. If a student submits a program without any breakpoints, the question
will behave as it did before: the debugger will not be invoked, the submission will
receive a grade, and the results of each test case will be generated for the student to
view.
Figure 4-1: A RISC-V assembly checker question after a student submits code in-
cluding a breakpoint instruction.
60
online checkers must ultimately be run on a physical microcontroller. Lastly, a toggle
button appears that, when clicked, will show or hide the debuggers.
Every test case in the checker has a separate debugger that is generated auto-
matically when a student submits code with a breakpoint. Each debugger highlights
the instruction that is about to be run and also shows the instructions immediately
surrounding it. It also shows the stack and has collapsible containers for registers and
memory. The debugger is initially paused at the beginning of the submitted program,
and there are three buttons that the user can use to interact with the program:
61
Figure 4-2: The initial state of a RISC-V debugger instance, prior to executing any
of the program’s instructions. The highlighted instruction is the one that is about to
be executed.
Figure 4-3: The RISC-V assembly debugger highlights the register modified by the
most recently executed instruction. Here, the previous instruction, addi x7, x0, 40
modified register x7, so it is highlighted in the “Registers” section of the debugger.
62
it must be modified by an instruction that accesses memory. Otherwise, the user
can manually search for specific memory addresses using the search bar. Similar to
registers, a memory address will be highlighted for emphasis if the previous instruction
had written to it or if the user had searched for it. This behavior is displayed in Figure
4-4.
Figure 4-4: The RISC-V assembly debugger highlights either the memory address
either modified by the instruction executed most recently or an address explicitly
searched for by the user. Here, the user had searched for the memory address 0x724,
so the contents of that and the surrounding memory addresses are shown.
Students can also monitor the evolution of the stack throughout a program in the
“Stack” window. To illustrate the last-in-first-out nature of the stack, the stack shown
on the debugger grows as the stack pointer decrements and shrinks as it increments.
The stack is displayed separately from the rest of memory to emphasize how it grows
and shrinks. If the stack grows so that it no longer fits in the provided window, the
user can scroll to view all of its contents. An example stack is shown in Figure 4-5.
63
Figure 4-5: The RISC-V assembly debugger displays the stack section of memory
separately, highlighting the element added to the stack by the previous instruction.
The RISC-V debugging tool is integrated with the existing RISC-V assembly checker
questions located on the assignment’s webpage to reduce the overhead associated with
using it compared to if we were to use a separate debugging tool. An overview of
the new RISC-V assembly checker with debugging functionality is provided in Figure
4-6. If a student would like to use the debugger, they must simply submit a RISC-V
assembly program that includes a breakpoint instruction. Once finished debugging,
they must remove all breakpoints from their program and submit it again to receive
a grade.
It is imperative that the original functionality of the online RISC-V checker questions
be preserved after adding the debugging capabilities, as students still need to use these
checkers to submit and receive grades for their RISC-V assembly language programs.
When a student submits their RISC-V assembly code on the website, a Python
function is invoked to handle the submission. Originally, this function iterated through
every test case, comparing the behavior of the student’s submission agsinst the so-
lution and assigning a grade accordingly. After adding debugging capabilities, this
behavior comparison and grade assignment only occurs when a student submits a
RISC-V assembly program that does not contain a breakpoint.
64
Figure 4-6: Overview of the RISC-V checker question with debugging capability. The
debugger is activated when a student submits code including a breakpoint instruction
named csbreak. Otherwise, the debugger will not be activated and the submitted
code will receive a grade.
65
4.2.2 Identifying Breakpoints and Instructions to Log
The debugger is only activated when the submitted RISC-V assembly program con-
tains a breakpoint, so each submission must first be scanned to identify the presence
or absence of breakpoints in the program. We implemented this scan using RiscEmu
in a sandboxed environment to parse every instruction in the program. The program
counter is initialized to the first memory address containing an instruction, and it is
incremented until has gone through the entirety of instruction memory.
Because this breakpoint instruction needs to be parsed and used like any other
RISC-V instruction, we added a custom instruction named csbreak to the RiscEmu
instruction set. It behavior is comparable to that of a no-operation instruction – the
checker software just uses its presence or absence to determine whether or not the
debugger should be activated. If a csbreak instruction is encountered during this
preliminary scan of the program, a Boolean flag is set to indicate that the debugger
must be activated. Otherwise, the original behavior of the RISC-V assembly checker
is carried out and the submission will receive a grade.
When a student’s submission is run, it is integrated with additional test code that
may include solutions to other procedures they may have to write to complete the
assignment. For example, an assignment could instruct students to write a procedure
to calculate the sum of squares of values in an array, and this sum of squares procedure
must call a multiplication procedure that is defined and implemented in the test
code. It would be undesirable for the debugger to reveal the implementation of this
multiplication procedure in the case that students need to implement it at some point
during the class, so the debugger may only reveal instructions that are implemented
by the student. This requires the checker to determine which instructions may be
logged for debugging purposes.
During the checker’s initial scan for breakpoints, which is run in a sandboxed en-
vironment, a Python dictionary mapping each memory address encountered during
the scan to whether or not the behavior of the corresponding instruction should be
logged for debugging purposes is created. To determine whether or not an instruction
66
and its behavior should be logged, the code submitted by the student must be dis-
tinguished from the test code. We added two more custom instructions to RiscEmu,
named startlog and stoplog, that simply mark the beginning and end of the stu-
dent’s code submission, as shown in Figure 4-7. These instructions do not modify
the state of the system and are each implemented as a no-operation instruction for
the RiscEmu emulator, just like the csbreak instruction used to set breakpoints. We
assume that RiscEmu stores instructions in memory contiguously, so that the instruc-
tions encountered between startlog and stoplog are those written by the student.
Additionally, this initial scan maps each program counter to the instruction stored
at the corresponding memory address. Only instructions that should be logged for
debugging purposes, as described previously, are included in this mapping.
After the initial program scan, the RISC-V assembly checker’s submission function
has the information necessary to determine whether or not the debugger should be
activated. If the debugger is activated, it will run the program submitted by the stu-
dent against each test case in a sandboxed environment using RiscEmu. Additionally,
during program execution, RiscEmu is also used to gather and log the state of the
emulated system before specific instructions are run. This program state is stored in
a Python dictionary that is ultimately converted to a JSON object to be used in the
front-end JavaScript code that renders the debugger on the webpage. Examples of
entries in this JSON object are shown in Figures 4-8, 4-9, 4-10, and 4-11.
Using RiscEmu extensively, the program creates and configures an instance of a
RV32I processor, loads the program, consisting of the student submission and test
code as shown in Figure 4-7, into its memory. Then, the emulated processor executes
the program. For each instruction, the program first uses the current program counter
to determine whether or not the current system state should be logged. If it should
not be logged, the emulated processor simply executes the instruction and proceeds
to the next one. If the instruction should be logged, the current state of the system
must be saved.
67
Figure 4-7: The structure of the assembly code used for the initial scan to identify
breakpoints and distinguish the assembly instructions submitted by the student from
the assembly instructions injected by course staff.
68
We use a Python dictionary to store the state of the system at each necessary
point in the program before executing the instruction. The state consists of:
• inscount: The number of instructions that have been executed since the be-
ginning of the program, including those not logged for debugging purposes.
• final_state: Indicates whether or not this state is the final one of the program.
• new_mem_vals: The updated memory value. If the previous instruction was not
logged, or the current instruction is a breakpoint, it includes all memory values
of interest.
Each “current state” Python dictionary, like the ones shown in Figures 4-8, 4-9, 4-10,
and 4-11, is then added to an “overall state” Python dictionary, and its key is the
number of instructions that have been logged since the beginning of the program.
This data structure is used extensively when rendering the front-end of the debugger.
69
1 "0": {
2 " pc ": 460 ,
3 " ins ": " addi a0 , x0 , 1" ,
4 " inscount ": 1 ,
5 " final_state ": false ,
6 " updated_register ": null ,
7 " updated_addr ": null ,
8 " new_mem_vals ": {} ,
9 " new_reg_vals ": {
10 " zero ": 0 ,
11 " ra ": " Undefined " ,
12 " sp ": 524792 ,
13 " gp ": " Undefined " ,
14 " tp ": " Undefined " ,
15 " t0 ": " Undefined " ,
16 " t1 ": " Undefined " ,
17 " t2 ": " Undefined " ,
18 " s0 ": " Undefined " ,
19 " s1 ": " Undefined " ,
20 " a0 ": " Undefined " ,
21 " a1 ": " Undefined " ,
22 " a2 ": " Undefined " ,
23 " a3 ": " Undefined " ,
24 " a4 ": " Undefined " ,
25 " a5 ": " Undefined " ,
26 " a6 ": " Undefined " ,
27 " a7 ": " Undefined " ,
28 " s2 ": " Undefined " ,
29 " s3 ": " Undefined " ,
30 " s4 ": " Undefined " ,
31 " s5 ": " Undefined " ,
32 " s6 ": " Undefined " ,
33 " s7 ": " Undefined " ,
34 " s8 ": " Undefined " ,
35 " s9 ": " Undefined " ,
36 " s10 ": " Undefined " ,
37 " s11 ": " Undefined " ,
38 " t3 ": " Undefined " ,
39 " t4 ": " Undefined " ,
40 " t5 ": " Undefined " ,
41 " t6 ": " Undefined "
42 },
43 " stack_top ": 524792 ,
44 " stack_bottom ": 524792 ,
45 " stack_data ": {}
46 },
47
Figure 4-8: An example of the first entry in a program state JSON object. It records
the state of the system before the first student-written instruction is executed.
70
1 "1": {
2 " pc ": 464 ,
3 " ins ": " addi sp , sp , -4" ,
4 " inscount ": 2 ,
5 " final_state ": false ,
6 " updated_register ": " a0 " ,
7 " updated_addr ": null ,
8 " new_reg_vals ": {
9 " a0 ": 1
10 },
11 " new_mem_vals ": {} ,
12 " stack_top ": 524792 ,
13 " stack_bottom ": 524792 ,
14 " stack_data ": {}
15 },
16
Figure 4-9: An example entry in the program state JSON object, where the previ-
ous instruction had modified register a0. This information is communicated via the
updated_register and new_reg_vals fields in the entry. Note that this is the state
of the system before the instruction included in this entry, addi sp, sp, -4, is exe-
cuted.
1 "3": {
2 " pc ": 472 ,
3 " ins ": " lui t0 , 6" ,
4 " inscount ": 4 ,
5 " final_state ": false ,
6 " updated_register ": null ,
7 " updated_addr ": 524788 ,
8 " new_reg_vals ": {} ,
9 " new_mem_vals ": {
10 "524788": 1
11 },
12 " stack_top ": 524788 ,
13 " stack_bottom ": 524792 ,
14 " stack_data ": {
15 "524788": 1
16 }
17 },
18
19
Figure 4-10: An example entry in the program state JSON object, where the previous
instruction had written the value 1 to the memory address 524788. This is communi-
cated through the updated_addr and new_mem_vals fields in the entry. This memory
address modified was also a part of the stack, which is determined using the fields
stack_top and stack_bottom, so the stack_data field is also updated to reflect this
memory write.
71
1 "5": {
2 " pc ": 480 ,
3 " ins ": " csbreak " ,
4 " inscount ": 6 ,
5 " final_state ": false ,
6 " u p d a t e d _ r e g i s t e r ": null ,
7 " updated_addr ": 24576 ,
8 " new_mem_vals ": {
9 "24576": 1 ,
10 "524788": 1
11 },
12 " new_reg_vals ": {
13 " zero ": 0 ,
14 " ra ": " Undefined " ,
15 " sp ": 524788 ,
16 " gp ": " Undefined " ,
17 " tp ": " Undefined " ,
18 " t0 ": 24576 ,
19 " t1 ": " Undefined " ,
20 " t2 ": " Undefined " ,
21 " s0 ": " Undefined " ,
22 " s1 ": " Undefined " ,
23 " a0 ": 1 ,
24 " a1 ": " Undefined " ,
25 " a2 ": " Undefined " ,
26 " a3 ": " Undefined " ,
27 " a4 ": " Undefined " ,
28 " a5 ": " Undefined " ,
29 " a6 ": " Undefined " ,
30 " a7 ": " Undefined " ,
31 " s2 ": " Undefined " ,
32 " s3 ": " Undefined " ,
33 " s4 ": " Undefined " ,
34 " s5 ": " Undefined " ,
35 " s6 ": " Undefined " ,
36 " s7 ": " Undefined " ,
37 " s8 ": " Undefined " ,
38 " s9 ": " Undefined " ,
39 " s10 ": " Undefined " ,
40 " s11 ": " Undefined " ,
41 " t3 ": " Undefined " ,
42 " t4 ": " Undefined " ,
43 " t5 ": " Undefined " ,
44 " t6 ": " Undefined "
45 },
46 " stack_top ": 524788 ,
47 " stack_bottom ": 524792 ,
48 " stack_data ": {
49 "524788": 1
50 }
51 },
52
Figure 4-11: An example entry in the program state JSON object, where the cur-
rent instruction is a csbreak instruction, indicating that the program has reached a
breakpoint. Due to the way the state of the system is rendered on the front-end of
the debugger, the entire state of the system must be logged at each breakpooint.
72
4.2.4 Debugger Front-end
The central submission Python function included in the checker software will condi-
tionally generate certain HTML elements depending on whether or not the debugger
is activated. A separate debugger is instantiated for every individual test case, so
that interactions with the debugger for one test case does not impact the status of
any of the other debuggers. After initially being rendered, these HTML elements are
each controlled by JavaScript listener functions that respond to user interaction in
the form of button clicks. There are four buttons that a user can interact with: Step,
Continue, Reset, and Memory Search.
First, JavaScript objects are generated so that information about the program’s
state can be used for the debugger’s front end. The first object is simply the dictionary
containing the state of the system, as defined in the previous section, throughout
program execution.
The second object contains information about the system state that should be
rendered on the debugger at a single point in time, as shown in Figure 4-12. It has
the following properties:
• done: Whether or not the debugger has reached the end of the program.
• reg_state: A mapping between the 32 registers and their values at the current
point in time.
• min_pc: The lowest memory address containing an instruction from the stu-
dent’s code submission.
• max_pc: The highest memory address containing an instruction from the stu-
dent’s code submission.
73
1 {
2 " ins ": 1 ,
3 " done ": false ,
4 " reg_state ": {
5 " zero ": 0 ,
6 " ra ": " Undefined " ,
7 " sp ": 524800 ,
8 " gp ": " Undefined " ,
9 " tp ": " Undefined " ,
10 " t0 ": " Undefined " ,
11 " t1 ": " Undefined " ,
12 " t2 ": " Undefined " ,
13 " s0 ": " Undefined " ,
14 " s1 ": " Undefined " ,
15 " a0 ": 0 ,
16 " a1 ": " Undefined " ,
17 " a2 ": " Undefined " ,
18 " a3 ": " Undefined " ,
19 " a4 ": " Undefined " ,
20 " a5 ": " Undefined " ,
21 " a6 ": " Undefined " ,
22 " a7 ": " Undefined " ,
23 " s2 ": " Undefined " ,
24 " s3 ": " Undefined " ,
25 " s4 ": " Undefined " ,
26 " s5 ": " Undefined " ,
27 " s6 ": " Undefined " ,
28 " s7 ": " Undefined " ,
29 " s8 ": " Undefined " ,
30 " s9 ": " Undefined " ,
31 " s10 ": " Undefined " ,
32 " s11 ": " Undefined " ,
33 " t3 ": " Undefined " ,
34 " t4 ": " Undefined " ,
35 " t5 ": " Undefined " ,
36 " t6 ": " Undefined "
37 },
38 " mem_state ": {} ,
39 " min_pc ": 460 ,
40 " max_pc ": 492 ,
41 " pc_to_inst ": {
42 "460": " addi a0 , x0 , 0" ,
43 "464": " addi a1 , x0 , 0" ,
44 "468": " addi a2 , x0 , 40" ,
45 "472": " csbreak " ,
46 "476": " bge a0 , a2 , end " ,
47 "480": " lw a4 , 1792( a0 ) " ,
48 "484": " add a1 , a1 , x8 " ,
49 "488": " addi a0 , a0 , 4" ,
50 "492": " jal x0 , loop "
51 },
52 " addr_viewing ": null
53 }
54
Figure 4-12: An example of the JavaScript object containing data about the current
state of the system that is used to render the front-end of the debugger at that point
in time.
74
• pc_to_inst: A mapping between each program counter and its corresponding
instruction, for every instruction in the program submitted by the student.
The third object contains variables that refer to each HTML element that will either
invoke or be updated by JavaScript event listeners. These HTML elements are: a step
button, a reset button, a continue button, a container to display the registers and
their values, a container to display memory addresses and their values, a memory
search bar, a memory search button, a memory search error message container, a
container to display the stack, a container to highlight the current instruction, and
an instruction count container. These three objects are then passed into a JavaScript
function that will provide them with the necessary functionality.
There are four main JavaScript functions that are invoked by user interaction –
step, continue, reset, and memory search. The step operation simulates the execution
of a single instruction, the operation function simulates the execution of multiple
instructions until a breakpoint is encountered, the reset operation resets the state of
the debugger to be at the beginning of the program, and the memory search operation
allows the user to view the contents of memory at certain addresses.
75
76
Chapter 5
Discussion
Throughout these offerings, we have collected data and information from in-class
surveys and observation that can inform the effectiveness and impact of the class.
5.1 Enrollment
One key metric of a class is its enrollment, because it is an indicator of overall stu-
dent interest in the class as well as the amount of resources needed for its successful
execution. Approximately 622 students have taken 6.190 across the first five offerings
77
of the 6.190, as shown in Table 5.1. This is not the number of unique students who
have taken the class, as students have repeated the class under circumstances such
as receiving a non-passing grade. We consider enrollment to be the number of stu-
dents who have completed the class as demonstrated by taking the final exam. As of
writing, the final exam had not yet been administered for the SP23 H2 offering of the
class, so we define that term’s enrollment as the number of students. taking the class
for-credit, who are eligible to submit a MIT-administered subject evaluation for the
class.
The very first offering of the class, during SP22 H2, had just eight students.
Enrollment for that quarter was low for three main reasons. First, it was the initial,
pilot offering of the class, so students may not have heard about it or planned to
take it. Additionally, only students entering MIT in fall 2022, the following semester,
would need to take 6.190 to fulfill the new requirements for the Course 6-2 or Course
6-3 majors [1]. Students who were enrolled at MIT before the fall 2022 semester could
choose between completing the old major requirements, that did not include 6.190,
and the new ones. Finally, 6.190 was not an enforced corequisite for 6.191 until the
spring 2023 semester, one year later [1]. Consequently, the enrollment figure for the
second half of the spring 2022 semester can be treated as an outlier, as the class was
offered under various conditions that were unique to that academic quarter and are
no longer applicable.
Student enrollment in the class quickly grew. It increased to 89 students in the
FA22 H1 offering and reached a maximum of 199 students during the FA22 H2 offering
78
of the class. This increase between the first and second halves of the semester could,
in part, be due to students needing to complete the prerequisite class, 6.100A, prior
to enrolling in 6.190. Because 6.100A is offered during the first half of the semester,
students could conveniently take 6.100A during the first half and 6.190 during the
second. Additionally, word may have simply started to spread about the class itself.
Enrollment then declined slightly, decreasing to 176 students in the SP23 H1
offering and 150 students in the SP23 H2 offering. The spring 2023 semester was the
first where 6.190 was an enforced corequisite for 6.191, so many students took 6.190
during the first half so that they could enroll in 6.191.
If we consider only the four offerings of the class throughout the 2022-2023 aca-
demic year, this class was offered to 614 students. On average, there were 153.5
students per offering. In the future, 6.190 will only be offered twice per year, during
both halves of the spring semester. Under this planned decrease in annual offerings
of the class, if the number of students who need to take the class annually remains
the same, at 614, the average enrollment would double to 312 students per offering.
The maximum number of students taking the class during a given offering thus far
has been 199, so this potential significant increase in enrollment must be considered
when allocating resources, such as staff, toward the 6.190 in the future.
79
basis allows us to identify weeks, and even particular assignments, that require a
different amount of time than expected. It is important to emphasize that this data
is self-reported by the students, so it may not be a completely accurate representation
of the actual amount of time students spent on the class. However, this data does
provide us with an idea of how much time students devoted toward completing the
class.
A typical week in 6.190 consists of five main components: a lecture, a recitation,
a lab, a postlab, and exercises. Lecture and recitation are each scheduled for an hour
and a half every week, and our calculations assume that the average student is in
attendance at both of these class sessions weekly.
On average, the self-reported weekly time commitment for 6.190 is 12.08 hours,
as shown in Table 5.2, which is very close to the expected weekly time commitment
of 12 hours – it is only about five minutes more than expected. This overall weekly
average is broken down into 3 hours spent in lecture and recitation, 4.02 hours on
exercises, 3.04 hours on the lab, and 2.01 hours on the postlab. Week 5, which is
dedicated to RISC-V calling conventions and the stack, had the largest time commit-
ment, with students, on average, dedicating 13.14 hours to it. Week 1, which focuses
on introducing students to C and the idea of manipulating individual bits in memory,
had the smallest time commitment, with students spending an average of 10.26 hours
on the class that week.
During the final week of each of the first three offerings of the course, we administered
a survey prompting students to indicate their level of agreement with various state-
ments on a scale of 0 through 4. A rating of 0 means that the respondent strongly
disagreed with the statement, a rating of 2 means that the respondent was neutral to-
ward the statement, and a rating of 4 means that the respondent strongly agreed with
the statement. The first set of statements pertained to how students felt that various
course components contributed to their learning. We can use this feedback to eval-
80
Week 1 2 3 4 5 Overall (Average)
Lecture 1.50 1.50 1.50 1.50 1.50 1.50
Recitation 1.50 1.50 1.50 1.50 1.50 1.50
Exercises 2.90 4.11 4.54 4.32 4.24 4.02
Lab 2.63 3.44 3.04 2.88 3.23 3.04
Postlab 1.73 2.09 1.80 1.77 2.67 2.01
Total (Hours) 10.26 12.64 12.39 11.97 13.14 12.08
Table 5.2: The average self-reported amount of time (in hours) that a student would
spend on Introduction to Low-level Programming in C and Assembly weekly. The
overall average is provided, as well as averages for each week and course component.
The data pertaining to the time spent on exercises, labs, and postlabs was collected
from weekly surveys administered to students during the first three offerings of the
course. We assume that the average student attends the 1.5-hour lecture and 1.5-hour
recitation each week.
uate the effectiveness of the different aspects of the course and make improvements
accordingly. The statements provided in the survey and their average responses are
shown in Table 5.3. Additionally, histograms presenting the frequency distributions
of the responses to each statement are shown in Figures 5-1 and 5-2.
Students found the labs to be the aspect of the class most helpful to their under-
standing of a given week’s material, rating their agreement with the statement, “The
labs helped me better understand the material presented in lecture and recitation,”
3.65/4 on average. The matching statements about hands-on components of the class,
such as the postlabs and exercises, that required students to practice applying the
concepts learned in lecture and recitation, were also generally agreed with. However,
students found more the more traditional class session components of the course least
helpful, rating their agreement with the statements, “The lectures were helpful to my
learning,” and “The recitations helped me better understand the material presented
in lecture” as 2.79/4 and 2.82/4, respectively. While both of these average responses
indicate agreement, they indicate less agreement than those to affirmative statements
about other aspects of the course.
On average, students agreed with the statement that they were provided with
enough exercises to gain comfort with the course material, however, they also were
81
Figure 5-1: Histograms displaying the frequencies of student responses to first six
final survey prompts pertaining to their experience with the different components of
the class. A response of 0 indicates that the respondent strongly disagreed with the
statement, a 2 indicates that the respondent was neutral towards the statement, and
a 4 indicates that the respondent strongly agreed with the statement.
82
Figure 5-2: Histograms displaying the frequencies of student responses to the seventh
through ninth final survey prompts pertaining to their experience with the different
components of the class. A response of 0 indicates that the respondent strongly dis-
agreed with the statement, a 2 indicates that the respondent was neutral towards the
statement, and a 4 indicates that the respondent strongly agreed with the statement.
83
Prompt Average Standard Deviation Respondents
The lectures were helpful 2.79 1.05 208
to my learning.
The recitations helped me better 2.82 1.16 211
understand the material presented
in lecture.
The labs helped me better 3.65 0.66 210
understand the material
presented in lecture and
recitation.
The postlabs helped me better 3.31 0.80 210
understand the material presented
in lecture and recitation.
I felt prepared to complete the 3.39 0.80 210
postlab independently after
completing the lab.
I enjoyed working with physical 3.33 1.06 211
hardware during the labs and
postlabs.
The exercises helped me better 3.29 0.90 208
understand the material presented
in lecture and recitation.
There were enough exercises for 3.43 0.83 210
me to become comfortable with
the material.
There were too many exercises. 2.80 0.83 210
in slight agreement with the statement that there were too many exercises. We
responded to this feedback by evaluating the necessity of each of the exercises that
were assigned to students and making adjustments to them accordingly. For example,
Week 3 initially contained an exercise that required students to implement a nontrivial
function in Python. We found that this exercise wasn’t reinforcing any concepts
84
covered in the class and removed it.
Students reporting that they found the labs and postlabs to be helpful, and that
they enjoyed working with hardware during those assignments, particularly validates
the decision to incorporate an embedded systems laboratory component into the
curriculum of the class.
The final class survey discussed in the previous section included a second set of state-
ments that primarily asked about the student’s perceived understanding of various
course topics. These responses allow us to gain insight to what students felt they
had learned from taking the class and compare that to what we would have expected
them to learn. The statements and their average responses, on a scale from 0 to 4
as defined previously, are displayed in Table 5.4. Additionally, histograms presenting
the frequency distributions of the responses to each statement are shown in Figures
5-3, 5-4, and 5-5.
According to these survey responses, students most strongly agreed that they
understood how C pointers are translated into RISC-V assembly instructions and how
to translate between different numerical representations. Students agreed least with
the statements that they understood how floating point numbers and C structures
are represented in memory.
One promising insight gained from the responses to these survey prompts is that
students overall felt as though they had some level of understanding of each of the
topics mentioned in the survey. The average student response to every statement,
which affirmed understanding of a particular topics or action, was greater than the
neutral response of 2. However, it is possible that these average figures are inflated
due to this data being self-reported.
One goal of this class is for other, more advanced, classes offered by the MIT
Department of Electrical Engineering and Computer Science to be able to use the C
programming language without needing to use class time to introduce it to students.
85
Prompt Average Standard Deviation Respondents
I am able to write programs using 3.44 0.67 209
the C programming language
I understand how data is 3.39 0.72 209
represented in memory
I understand how to access data 3.41 0.73 209
stored in memory using C
I feel comfortable using pointers 3.24 0.77 209
in C
I can translate C programs into 3.50 0.61 208
RISC-V assembly code
I understand how C pointers 3.67 0.52 6
translate into RISC-V assembly
instructions.
I understand how to use RISC-V 3.49 0.64 200
assembly instructions to access
and modify memory.
I understand how different areas 3.19 0.81 205
of memory are used throughout a
program
I am comfortable translating 3.60 0.64 207
between decimal, binary, and
hexadecimal encoding of numbers.
I am comfortable using two’s 3.40 0.79 206
complement encoding of numbers.
I am comfortable using floating 2.62 1.16 207
point representation for numbers.
I understand how characters and 3.40 0.73 207
strings are represented in memory.
I understand how structs are 2.87 0.97 199
represented in memory.
Th students who responded to the survey, on average, agreed with the statement
that they were able to write programs using the C programming language, and they
responded favorably to the other C-related prompts. This indicates that students,
on average, comfortable with the C programming language, meaning that they would
86
potentially be able to use it in future coursework without needing to dedicate a
significant amount of time to becoming familiar with it.
87
Figure 5-3: Histograms displaying the frequencies of student responses to the first
six final survey prompts pertaining to their perceived understanding of main course
topics and comfort with using them. A response of 0 indicates that the respondent
strongly disagreed with the statement, a 2 indicates that the respondent was neutral
towards the statement, and a 4 indicates that the respondent strongly agreed with
the statement.
88
Figure 5-4: Histograms displaying the frequencies of student responses to the seventh
through twelfth final survey prompts pertaining to their perceived understanding of
main course topics and comfort with using them. A response of 0 indicates that the
respondent strongly disagreed with the statement, a 2 indicates that the respondent
was neutral towards the statement, and a 4 indicates that the respondent strongly
agreed with the statement.
89
Figure 5-5: Histogram displaying the frequencies of student responses to the thir-
teenth final survey prompt pertaining to their perceived understanding of main course
topics and comfort with using them. A response of 0 indicates that the respondent
strongly disagreed with the statement, a 2 indicates that the respondent was neutral
towards the statement, and a 4 indicates that the respondent strongly agreed with
the statement.
90
Chapter 6
Future Work
After developing this course and running it for five academic quarters, we have iden-
tified four viable extensions of this work. Additionally, it will be important for future
instructors of the course to continuously evaluate the effectiveness and relevance of
the existing materials and make improvements accordingly.
91
major operating systems and processors would significantly reduce the amount of
time students and staff members spend troubleshooting software and hardware issues
associated with system setup during the first week of the class. A future project could
involve exploring options, such as using Docker, for enabling this.
A typical student taking this course has only ever been exposed to basic, high-level
programming in Python, so they may find C programs difficult to understand, es-
pecially early in the course. They could potentially benefit from a debugging tool
that would allow them to walk through C programs and observe how memory and
the different program variables evolve as the lines are run. Similar to the RISC-V
debugging tool, this could be integrated with existing C program checker questions
on the course website.
A C program debugging tool would be useful for troubleshooting C programs, but
it also has some potentially-significant educational benefits. Strategically-designed
visuals could emphasize the different types of variables and data structures and how
they are stored in memory. For example, the tool could clearly display that a uint8_t
variable, upon declaration, occupies a single byte in memory, while a uint16_t vari-
able occupies two. Additionally, it could display more complex ideas such as how
pointers reference values in memory while still themselves being stored in memory.
One significant constraint faced during the design and development of 6.190 was the
short, half-semester length. This only allows for about six weeks’ worth of material,
and schedule constraints require us to administer the final exam during the sixth week
of the course, so it does not test students on the material introduced during the sixth
week of classes, which includes dynamic memory allocation. Additionally, there is
no lab or postlab this week. Ideally, students would get the opportunity to explore
92
dynamic memory allocation in an assignment and be asked questions about it during
the exam, because this topic is integral to understanding how memory is managed in
C programs.
In addition to allowing time for proper coverage of dynamic memory allocation,
extending the course to last for an entire semester would allow for the coverage of more
related topics, including compilers, software performance and optimization, and oper-
ating systems. The additional time would provide the ability to administer multiple
quizzes throughout the semester, rather than just one final exam, and the possibility
of a guided or open-ended final project could be explored.
This class (6.190) was designed with MIT Computation Structures (6.191) in mind:
moving assembly language content from 6.191 to 6.190 would allow 6.191 to introduce
more topics related to computing systems and explore existing ones in more depth.
6.190 became an enforced corequisite for MIT Computation Structures (6.191) in
the Spring 2023 semester, so 6.191 updated accordingly – new content includes a
more in-depth treatment of virtual memory and exceptions and a lecture dedicated
to parallel processing. Additionally, because 6.190 now covers the RISC-V assembly
content that the first two 6.191 labs had focused on, 6.191 has replaced those labs
with two new ones: the first focuses on implementing a four-stage pipelined RISC-V
processor with full bypassing (this had previously been an optional design exercise),
and the second focuses on operating systems. Additionally, 6.191 has introduced
a new optional design project, as a primary aspect of the previous one is now the
required four-stage pipelined processor lab. As these changes were all introduced in
the Spring 2023 semester, their success could not be evaluated as part of this thesis.
Future work could explore 6.190’s impact on 6.191 by analyzing the changes 6.191
made in response to the inclusion of 6.190.
Additionally, the changes made to 6.191 would allow its follow-on classes to adjust
accordingly. More future work could analyze how the inclusion of 6.190 ultimately
93
impacts classes taken later in the curriculum, such as 6.192 (Constructive Computer
Architecture) and 6.205 (Digital Systems Laboratory), due to the changes it allowed
6.191 to make.
94
Appendix A
Code Appendix
4 void setup () {
5 /* Setup function to configure GPIO pins and real - time clock .
Today , the real - time
6 clock has already been configured for you . */
7
95
15 }
16
17 void app_main () {
18 /* Main function , your program starts here . Any functions
19 you call here must be defined above or in 6 s077 . h . */
20
21 setup () ;
22
26 while (1) {
27 // main loop of our program
28
7 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
8 // Peripheral Base Addresses //
9 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
10
96
11 # define GPIO_BASE_ADDR 0 x60004000 // Base address for memory
locations dedicated to GPIO matrix
12 # define IO_MUX_BASE_ADDR 0 x60009000 // Base address for memory
locations dedicated to IO multiplexer addresses
13 # define TIMER_BASE_ADDR 0 x6001F000 // Base address for memory
locations dedicated to timer
14
15
16 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
17 // General - Purposed Input / Output Definitions //
18 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
19
20 /* GPIO Addresses */
21 # define GPIO_OUT_ADDR ( GPIO_BASE_ADDR + 0 x0004 )
22 # define GPIO_ENABLE_ADDR ( GPIO_BASE_ADDR + 0 x0020 )
23 # define GPIO_IN_ADDR ( GPIO_BASE_ADDR + 0 x003C )
24 # define IO_ MUX_GPIO n_ADDR ( IO_MUX_BASE_ADDR + 0 x0004 ) // 1 memory
location / pin , need to add 4 n for pin n
25
32 // Switches
33 # define SW7 9
34 # define SW6 8
35 # define SW5 7
36 # define SW4 6
37 # define SW3 5
38 # define SW2 4
39 # define SW1 18
40 # define SW0 19
41
42 // Buttons
97
43 # define BTNF 9
44 # define BTNU 8
45 # define BTNC 7
46 # define BTND 6
47 # define BTNR 5
48 # define BTNL 4
49
50 // LEDs
51 # define LED1 2
52 # define LED2 3
53
54
55 /* GPIO Functions */
56 void pinSetup ( int pin_num , int mode ) {
57 /* Setup function to configure GPIO pin ( pin_num ) as either an
input
58 or an output , as defined by mode .
59
60 Arguments :
61 pin_num : GPIO pin number (0 - 31)
62 mode : Mode to configure pin as . GPIO_INPUT = 0 , GPIO_OUTPUT =
1
63 */
64
65 if ( mode == GPIO_INPUT ) {
66 // Configure GPIO pin pin_num as input
67 int * io_mux_gpio_addr = ( int *) ( IO_MUX_G PIOn_ADD R + (4*
pin_num ) ) ;
68 * io_mux_gpio_addr = // YOUR CODE HERE
69 } else if ( mode == GPIO_OUTPUT ) {
70 // Configure GPIO pin pin_num as output .
71 int * gpio_enable_addr = ( int *) GPIO_ENABLE_ADDR ;
72 * gpio_enable_addr = // YOUR CODE HERE
73 }
74 }
75
98
76
88 }
89
90
105 }
106
107 // / / / // / / / / / / / / / / / / / / / /
108 // Timer Definitions //
109 // / / / // / / / / / / / / / / / / / / / /
110
99
111 /* Timer Addresses */
112 # define TIM ER_CONFI G_ADDR ( TIMER_BASE_ADDR + 0 x0000 )
113 # define TI M E R_ C OU N T _L O _A D DR ( TIMER_BASE_ADDR + 0 x0004 )
114 # define TI M E R_ C OU N T _H I _A D DR ( TIMER_BASE_ADDR + 0 x0008 )
115 # define T I M E R _ C O U N T _ U P D A T E _ A D D R ( TIMER_BASE_ADDR + 0 x000C )
116
139
100
143
158
101
173 return (( bigger_timer_hi << 32) + timer_lo_val ) >> 4;
174 }
7 // TASK 1:
8 // Get updateLeds working below
9 void updateLeds ( int * leds ) {
10
13 }
14
15 // TASK 2:
16 // Get getInputs working below
17 int getInputs () {
18
25 // positions the 8 - long led array into the larger screen buffer :
26 void drawLedArray ( int leds ) {
102
27 screen_buffer [7] = leds < <8;
28 drawBuffer () ;
29 }
30
31 void app_main () {
32 timerSetup () ;
33 setupDisplay () ;
34 for ( int i =0; i <8; i ++) {
35 pinSetup ( switch_pins [ i ] , GPIO_INPUT ) ;
36 }
37 eraseBuffer () ;
38 int leds = 0 x10 ; // initial state of LEDs
39 int old_switches =0; // for remembering switches
40 drawLedArray ( leds ) ; // draw initial state on LED array
41 long timer = millis () ;
42 while (1) {
43 while ( millis () - timer < TIME ) { // wait for TIME milliseconds
44 int new_switches = getInputs () ;
45 if ( new_switches != old_switches ) {
46 leds = leds ; // TASK 3 ( update so new_switches
alters leds !)
47 drawLedArray ( leds ) ; // draw immediately
48 old_switches = new_switches ; // remember what the
switches were .
49 break ;
50 }
51 }
52 while ( millis () - timer < TIME ) ; // wait for remainder of loop
time
53 timer = millis () ; // update and remember
54 updateLeds (& leds ) ; // update LED array
55 drawLedArray ( leds ) ;
56 }
57 }
103
A.2.2 6.190 Header File Code for Driving LED Display
This code is added to the 6.190 header file that students used in Lab 1. Students will
use this file for the remainder of the labs, making slight modifications to it depending
on the assignment.
1 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
2 // Serial Peripheral Interface ( SPI ) Functions //
3 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
4
10 void spiPause () {
11 /* 1 microsecond delay for SPI timing */
12
13 uint64_t t = micros () ;
14 while ( micros () -t <1) ;
15 }
16
17
104
31 // Generate clock signal and send 1 bit / clock period
32 for ( int q =0; q < len ; q ++) {
33 for ( int p =0; p <8; p ++) {
34 pinWrite ( clk_pin ,0) ;
35 pinWrite ( mosi_pin ,(( data [ q ] > > p ) &0 x01 ) ) ;
36 spiPause () ;
37 pinWrite ( clk_pin ,1) ;
38 spiPause () ;
39 }
40 }
41 pinWrite ( cs_pin ,1) ; // bring chip - select signal high
42 }
43
44 // ///////////////
45 // LED Display //
46 // ///////////////
47
105
66 void setupDisplay () {
67 /* Function to set up LED display */
68
106
101 }
102
103 uint32_t screen_buffer [8]; // 8 x32 array for storing LED display
image
104
107
3
4 int switch_pins [8] = { SW0 , SW1 , SW2 , SW3 , SW4 , SW5 , SW6 , SW7 };
5
6 void setup () {
7
18 }
19
20
21 char switchToBinary () {
22 /*
23 Return :
24 char : 8 - bit value . Bits 0 -6 correspond to switch inputs , bit
7 is 0.
25 */
26
27 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
28 // TO - DO : Complete switchToBinary () //
29 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
30
31 return 0; // replace !
32 }
33
34
108
37 by filling in a buffer .
38
39 Argument :
40 char * string_to_draw : String to draw on the LED array .
41 */
42
43 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
44 // TO - DO : Complete drawAsciiString () //
45 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
46
47
48 }
49
50
51 void app_main () {
52 setup () ;
53
54 char ascii_input = 0;
55
56 while (1) {
57 ascii_input = switchToBinary () ;
58 printf ( " Integer value : 0 x %x , " , ascii_input ) ; // printing
this value using % x formatter gives a hexadecimal value ...
59 printf ( " ASCII character : % c \ n " , ascii_input ) ; // ...
printing the same value with % c formatter gives a character
60
66 // Code to slow down loop so that " bouncing " associated with
a single button press doesn ’t
67 // look like more than one button press . Write all of your
109
code in above this point in the loop .
68 int start = millis () ; // Get " start " time stamp
69 while ( millis () - start < 50) ; // Wait until current time
stamp - " start " is greater than 50 ms
70 }
71 }
This file is used for rendering ASCII characters on the labkit’s LED display. This
header file is derived from [10] – the sole difference is that we reversed the bits in each
byte in the array to improve compatibility with the existing laboratory infrastructure.
110
22 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0010
23 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0011
24 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0012
25 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0013
26 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0014
27 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0015
28 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0016
29 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0017
30 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0018
31 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0019
32 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +001 A
33 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +001 B
34 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +001 C
35 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +001 D
36 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +001 E
37 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +001 F
38 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0020 (
space )
39 { 0 x18 , 0 x3C , 0 x3C , 0 x18 , 0 x18 , 0 x00 , 0 x18 , 0 x00 } , // U +0021
(!)
40 { 0 x6C , 0 x6C , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0022
(")
41 { 0 x6C , 0 x6C , 0 xFE , 0 x6C , 0 xFE , 0 x6C , 0 x6C , 0 x00 } , // U +0023
(#)
42 { 0 x30 , 0 x7C , 0 xC0 , 0 x78 , 0 x0C , 0 xF8 , 0 x30 , 0 x00 } , // U +0024 (
$)
43 { 0 x00 , 0 xC6 , 0 xCC , 0 x18 , 0 x30 , 0 x66 , 0 xC6 , 0 x00 } , // U +0025
(%)
44 { 0 x38 , 0 x6C , 0 x38 , 0 x76 , 0 xDC , 0 xCC , 0 x76 , 0 x00 } , // U +0026
(&)
45 { 0 x60 , 0 x60 , 0 xC0 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0027
( ’)
46 { 0 x18 , 0 x30 , 0 x60 , 0 x60 , 0 x60 , 0 x30 , 0 x18 , 0 x00 } , // U +0028
(()
47 { 0 x60 , 0 x30 , 0 x18 , 0 x18 , 0 x18 , 0 x30 , 0 x60 , 0 x00 } , // U +0029
() )
111
48 { 0 x00 , 0 x66 , 0 x3C , 0 xFF , 0 x3C , 0 x66 , 0 x00 , 0 x00 } , // U +002 A
(*)
49 { 0 x00 , 0 x30 , 0 x30 , 0 xFC , 0 x30 , 0 x30 , 0 x00 , 0 x00 } , // U +002 B
(+)
50 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x30 , 0 x30 , 0 x60 } , // U +002 C
( ,)
51 { 0 x00 , 0 x00 , 0 x00 , 0 xFC , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +002 D
( -)
52 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x30 , 0 x30 , 0 x00 } , // U +002 E
(.)
53 { 0 x06 , 0 x0C , 0 x18 , 0 x30 , 0 x60 , 0 xC0 , 0 x80 , 0 x00 } , // U +002 F
(/)
54 { 0 x7C , 0 xC6 , 0 xCE , 0 xDE , 0 xF6 , 0 xE6 , 0 x7C , 0 x00 } , // U +0030
(0)
55 { 0 x30 , 0 x70 , 0 x30 , 0 x30 , 0 x30 , 0 x30 , 0 xFC , 0 x00 } , // U +0031
(1)
56 { 0 x78 , 0 xCC , 0 x0C , 0 x38 , 0 x60 , 0 xCC , 0 xFC , 0 x00 } , // U +0032
(2)
57 { 0 x78 , 0 xCC , 0 x0C , 0 x38 , 0 x0C , 0 xCC , 0 x78 , 0 x00 } , // U +0033
(3)
58 { 0 x1C , 0 x3C , 0 x6C , 0 xCC , 0 xFE , 0 x0C , 0 x1E , 0 x00 } , // U +0034
(4)
59 { 0 xFC , 0 xC0 , 0 xF8 , 0 x0C , 0 x0C , 0 xCC , 0 x78 , 0 x00 } , // U +0035
(5)
60 { 0 x38 , 0 x60 , 0 xC0 , 0 xF8 , 0 xCC , 0 xCC , 0 x78 , 0 x00 } , // U +0036
(6)
61 { 0 xFC , 0 xCC , 0 x0C , 0 x18 , 0 x30 , 0 x30 , 0 x30 , 0 x00 } , // U +0037
(7)
62 { 0 x78 , 0 xCC , 0 xCC , 0 x78 , 0 xCC , 0 xCC , 0 x78 , 0 x00 } , // U +0038
(8)
63 { 0 x78 , 0 xCC , 0 xCC , 0 x7C , 0 x0C , 0 x18 , 0 x70 , 0 x00 } , // U +0039
(9)
64 { 0 x00 , 0 x30 , 0 x30 , 0 x00 , 0 x00 , 0 x30 , 0 x30 , 0 x00 } , // U +003 A
(:)
65 { 0 x00 , 0 x30 , 0 x30 , 0 x00 , 0 x00 , 0 x30 , 0 x30 , 0 x60 } , // U +003 B
(;)
112
66 { 0 x18 , 0 x30 , 0 x60 , 0 xC0 , 0 x60 , 0 x30 , 0 x18 , 0 x00 } , // U +003 C
( <)
67 { 0 x00 , 0 x00 , 0 xFC , 0 x00 , 0 x00 , 0 xFC , 0 x00 , 0 x00 } , // U +003 D
(=)
68 { 0 x60 , 0 x30 , 0 x18 , 0 x0C , 0 x18 , 0 x30 , 0 x60 , 0 x00 } , // U +003 E
( >)
69 { 0 x78 , 0 xCC , 0 x0C , 0 x18 , 0 x30 , 0 x00 , 0 x30 , 0 x00 } , // U +003 F
(?)
70 { 0 x7C , 0 xC6 , 0 xDE , 0 xDE , 0 xDE , 0 xC0 , 0 x78 , 0 x00 } , // U +0040 (
@)
71 { 0 x30 , 0 x78 , 0 xCC , 0 xCC , 0 xFC , 0 xCC , 0 xCC , 0 x00 } , // U +0041 (
A)
72 { 0 xFC , 0 x66 , 0 x66 , 0 x7C , 0 x66 , 0 x66 , 0 xFC , 0 x00 } , // U +0042 (
B)
73 { 0 x3C , 0 x66 , 0 xC0 , 0 xC0 , 0 xC0 , 0 x66 , 0 x3C , 0 x00 } , // U +0043 (
C)
74 { 0 xF8 , 0 x6C , 0 x66 , 0 x66 , 0 x66 , 0 x6C , 0 xF8 , 0 x00 } , // U +0044 (
D)
75 { 0 xFE , 0 x62 , 0 x68 , 0 x78 , 0 x68 , 0 x62 , 0 xFE , 0 x00 } , // U +0045 (
E)
76 { 0 xFE , 0 x62 , 0 x68 , 0 x78 , 0 x68 , 0 x60 , 0 xF0 , 0 x00 } , // U +0046 (
F)
77 { 0 x3C , 0 x66 , 0 xC0 , 0 xC0 , 0 xCE , 0 x66 , 0 x3E , 0 x00 } , // U +0047 (
G)
78 { 0 xCC , 0 xCC , 0 xCC , 0 xFC , 0 xCC , 0 xCC , 0 xCC , 0 x00 } , // U +0048 (
H)
79 { 0 x78 , 0 x30 , 0 x30 , 0 x30 , 0 x30 , 0 x30 , 0 x78 , 0 x00 } , // U +0049 (
I)
80 { 0 x1E , 0 x0C , 0 x0C , 0 x0C , 0 xCC , 0 xCC , 0 x78 , 0 x00 } , // U +004 A (
J)
81 { 0 xE6 , 0 x66 , 0 x6C , 0 x78 , 0 x6C , 0 x66 , 0 xE6 , 0 x00 } , // U +004 B (
K)
82 { 0 xF0 , 0 x60 , 0 x60 , 0 x60 , 0 x62 , 0 x66 , 0 xFE , 0 x00 } , // U +004 C (
L)
83 { 0 xC6 , 0 xEE , 0 xFE , 0 xFE , 0 xD6 , 0 xC6 , 0 xC6 , 0 x00 } , // U +004 D (
M)
113
84 { 0 xC6 , 0 xE6 , 0 xF6 , 0 xDE , 0 xCE , 0 xC6 , 0 xC6 , 0 x00 } , // U +004 E (
N)
85 { 0 x38 , 0 x6C , 0 xC6 , 0 xC6 , 0 xC6 , 0 x6C , 0 x38 , 0 x00 } , // U +004 F (
O)
86 { 0 xFC , 0 x66 , 0 x66 , 0 x7C , 0 x60 , 0 x60 , 0 xF0 , 0 x00 } , // U +0050 (
P)
87 { 0 x78 , 0 xCC , 0 xCC , 0 xCC , 0 xDC , 0 x78 , 0 x1C , 0 x00 } , // U +0051 (
Q)
88 { 0 xFC , 0 x66 , 0 x66 , 0 x7C , 0 x6C , 0 x66 , 0 xE6 , 0 x00 } , // U +0052 (
R)
89 { 0 x78 , 0 xCC , 0 xE0 , 0 x70 , 0 x1C , 0 xCC , 0 x78 , 0 x00 } , // U +0053 (
S)
90 { 0 xFC , 0 xB4 , 0 x30 , 0 x30 , 0 x30 , 0 x30 , 0 x78 , 0 x00 } , // U +0054 (
T)
91 { 0 xCC , 0 xCC , 0 xCC , 0 xCC , 0 xCC , 0 xCC , 0 xFC , 0 x00 } , // U +0055 (
U)
92 { 0 xCC , 0 xCC , 0 xCC , 0 xCC , 0 xCC , 0 x78 , 0 x30 , 0 x00 } , // U +0056 (
V)
93 { 0 xC6 , 0 xC6 , 0 xC6 , 0 xD6 , 0 xFE , 0 xEE , 0 xC6 , 0 x00 } , // U +0057 (
W)
94 { 0 xC6 , 0 xC6 , 0 x6C , 0 x38 , 0 x38 , 0 x6C , 0 xC6 , 0 x00 } , // U +0058 (
X)
95 { 0 xCC , 0 xCC , 0 xCC , 0 x78 , 0 x30 , 0 x30 , 0 x78 , 0 x00 } , // U +0059 (
Y)
96 { 0 xFE , 0 xC6 , 0 x8C , 0 x18 , 0 x32 , 0 x66 , 0 xFE , 0 x00 } , // U +005 A (
Z)
97 { 0 x78 , 0 x60 , 0 x60 , 0 x60 , 0 x60 , 0 x60 , 0 x78 , 0 x00 } , // U +005 B
([)
98 { 0 xC0 , 0 x60 , 0 x30 , 0 x18 , 0 x0C , 0 x06 , 0 x02 , 0 x00 } , // U +005 C
(\)
99 { 0 x78 , 0 x18 , 0 x18 , 0 x18 , 0 x18 , 0 x18 , 0 x78 , 0 x00 } , // U +005 D
(])
100 { 0 x10 , 0 x38 , 0 x6C , 0 xC6 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +005 E
(^)
101 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 xFF } , // U +005 F (
_)
114
102 { 0 x30 , 0 x30 , 0 x18 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +0060
( ‘)
103 { 0 x00 , 0 x00 , 0 x78 , 0 x0C , 0 x7C , 0 xCC , 0 x76 , 0 x00 } , // U +0061 (
a)
104 { 0 xE0 , 0 x60 , 0 x60 , 0 x7C , 0 x66 , 0 x66 , 0 xDC , 0 x00 } , // U +0062 (
b)
105 { 0 x00 , 0 x00 , 0 x78 , 0 xCC , 0 xC0 , 0 xCC , 0 x78 , 0 x00 } , // U +0063 (
c)
106 { 0 x1C , 0 x0C , 0 x0C , 0 x7C , 0 xCC , 0 xCC , 0 x76 , 0 x00 } , // U +0064 (
d)
107 { 0 x00 , 0 x00 , 0 x78 , 0 xCC , 0 xFC , 0 xC0 , 0 x78 , 0 x00 } , // U +0065 (
e)
108 { 0 x38 , 0 x6C , 0 x60 , 0 xF0 , 0 x60 , 0 x60 , 0 xF0 , 0 x00 } , // U +0066 (
f)
109 { 0 x00 , 0 x00 , 0 x76 , 0 xCC , 0 xCC , 0 x7C , 0 x0C , 0 xF8 } , // U +0067 (
g)
110 { 0 xE0 , 0 x60 , 0 x6C , 0 x76 , 0 x66 , 0 x66 , 0 xE6 , 0 x00 } , // U +0068 (
h)
111 { 0 x30 , 0 x00 , 0 x70 , 0 x30 , 0 x30 , 0 x30 , 0 x78 , 0 x00 } , // U +0069 (
i)
112 { 0 x0C , 0 x00 , 0 x0C , 0 x0C , 0 x0C , 0 xCC , 0 xCC , 0 x78 } , // U +006 A (
j)
113 { 0 xE0 , 0 x60 , 0 x66 , 0 x6C , 0 x78 , 0 x6C , 0 xE6 , 0 x00 } , // U +006 B (
k)
114 { 0 x70 , 0 x30 , 0 x30 , 0 x30 , 0 x30 , 0 x30 , 0 x78 , 0 x00 } , // U +006 C (
l)
115 { 0 x00 , 0 x00 , 0 xCC , 0 xFE , 0 xFE , 0 xD6 , 0 xC6 , 0 x00 } , // U +006 D (
m)
116 { 0 x00 , 0 x00 , 0 xF8 , 0 xCC , 0 xCC , 0 xCC , 0 xCC , 0 x00 } , // U +006 E (
n)
117 { 0 x00 , 0 x00 , 0 x78 , 0 xCC , 0 xCC , 0 xCC , 0 x78 , 0 x00 } , // U +006 F (
o)
118 { 0 x00 , 0 x00 , 0 xDC , 0 x66 , 0 x66 , 0 x7C , 0 x60 , 0 xF0 } , // U +0070 (
p)
119 { 0 x00 , 0 x00 , 0 x76 , 0 xCC , 0 xCC , 0 x7C , 0 x0C , 0 x1E } , // U +0071 (
q)
115
120 { 0 x00 , 0 x00 , 0 xDC , 0 x76 , 0 x66 , 0 x60 , 0 xF0 , 0 x00 } , // U +0072 (
r)
121 { 0 x00 , 0 x00 , 0 x7C , 0 xC0 , 0 x78 , 0 x0C , 0 xF8 , 0 x00 } , // U +0073 (
s)
122 { 0 x10 , 0 x30 , 0 x7C , 0 x30 , 0 x30 , 0 x34 , 0 x18 , 0 x00 } , // U +0074 (
t)
123 { 0 x00 , 0 x00 , 0 xCC , 0 xCC , 0 xCC , 0 xCC , 0 x76 , 0 x00 } , // U +0075 (
u)
124 { 0 x00 , 0 x00 , 0 xCC , 0 xCC , 0 xCC , 0 x78 , 0 x30 , 0 x00 } , // U +0076 (
v)
125 { 0 x00 , 0 x00 , 0 xC6 , 0 xD6 , 0 xFE , 0 xFE , 0 x6C , 0 x00 } , // U +0077 (
w)
126 { 0 x00 , 0 x00 , 0 xC6 , 0 x6C , 0 x38 , 0 x6C , 0 xC6 , 0 x00 } , // U +0078 (
x)
127 { 0 x00 , 0 x00 , 0 xCC , 0 xCC , 0 xCC , 0 x7C , 0 x0C , 0 xF8 } , // U +0079 (
y)
128 { 0 x00 , 0 x00 , 0 xFC , 0 x98 , 0 x30 , 0 x64 , 0 xFC , 0 x00 } , // U +007 A (
z)
129 { 0 x1C , 0 x30 , 0 x30 , 0 xE0 , 0 x30 , 0 x30 , 0 x1C , 0 x00 } , // U +007 B
({)
130 { 0 x18 , 0 x18 , 0 x18 , 0 x00 , 0 x18 , 0 x18 , 0 x18 , 0 x00 } , // U +007 C
(|)
131 { 0 xE0 , 0 x30 , 0 x30 , 0 x1C , 0 x30 , 0 x30 , 0 xE0 , 0 x00 } , // U +007 D
(})
132 { 0 x76 , 0 xDC , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } , // U +007 E
(~)
133 { 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 , 0 x00 } // U +007 F
134 };
116
3
4 void setup () {
5
12
15 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
16 // TO - DO : Complete messageLength () //
17 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
18
19 return 0; // Replace !
20 }
21
22
25 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
26 // TO - DO : Complete fillScreenBuffer () //
27 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
28
29 }
30
31
32 void app_main () {
33 setup () ;
34
117
38 while (1) {
39 if ( offset == len * 8) {
40 offset = 0; // Reset if we have reached the end of the
message
41 } else {
42 eraseBuffer () ; // Clear screen buffer
43 fillScreenBuffer ( message , offset ) ; // TASK 2: FILL
SCREEN BUFFER WITH CORRECT DATA
44 drawBuffer () ; // Transmit screen buffer data to screen
45 offset ++; // Move forward
46 }
47
This starter code is the basis for both Lab 3 and Postlab 3.
5 // Constants
6 # define SCREEN_ROWS 8
7 # define SCREEN_COLS 32
8 enum snake_direction { up , down , left , right };
9
118
13
14 void setupRandom () {
15 int * r t c _ c n t l _ c lk _ c o n f _ r g = ( int *) R T C _ C N T L _ C L K _ C O N F _ R E G ;
16 * rt c _ c n t l _ c l k _ c o n f _ r g |= (1 << 10) ;
17 }
18
19
27
28 void setup () {
29 timerSetup () ;
30 setupRandom () ;
31 setupDisplay () ;
32 eraseBuffer () ;
33 int snake_buttons [5] = { BTNF , BTNU , BTND , BTNR , BTNL };
34 for ( int i = 0; i < 5; i ++) {
35 pinSetup ( snake_buttons [ i ] , GPIO_INPUT ) ;
36 }
37 }
38
39
119
45 Returns :
46 uint8_t representing the x - coordinate (0 -31) encoded in the
upper 5 bits of the argument .
47 */
48
49 // / / / / / / / / / / / / / / / / / / / / / / / / / / / /
50 // TO - DO : Implement getX () //
51 // / / / / / / / / / / / / / / / / / / / / / / / / / / / /
52
53 return 0; // replace
54 }
55
56
65 // / / / / / / / / / / / / / / / / / / / / / / / / / / / /
66 // TO - DO : Implement getY () //
67 // / / / / / / / / / / / / / / / / / / / / / / / / / / / /
68
69 return 0; // replace
70 }
71
72
120
76 uint8_t * location : a pointer to an unsigned 8 - bit value
representing a location on an 8 x32 gameboard .
77 uint8_t new_x : an unsigned integer representing the value
(0 -31) that we ’d like to set for the x location .
78 */
79
80 // / / / / / / / / / / / / / / / / / / / / / / / / / / / /
81 // TO - DO : Implement setX () //
82 // / / / / / / / / / / / / / / / / / / / / / / / / / / / /
83
84 }
85
86
94 // / / / / / / / / / / / / / / / / / / / / / / / / / / / /
95 // TO - DO : Implement setY () //
96 // / / / / / / / / / / / / / / / / / / / / / / / / / / / /
97 }
98
99
121
104 val : The binary (0 or 1) value indicating if the LED at the
location
105 should be off (0) or on (1) .
106 */
107
108 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
109 // TO - DO : Implement setPixel () //
110 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
111
112 }
113
114
122 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
123 // TO - DO : Implement drawBoard () //
124 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
125
126 }
127
128
135 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
136 // TO - DO : Implement updateSnake () //
122
137 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
138
139 }
140
141
153 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
154 // TO - DO : Complete generateFood () //
155 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
156
160
123
169
170 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
171 // TO - DO : Implement snakeAteFood () //
172 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
173
177
186 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
187 // TO - DO : Implement sn a ke C ol l i si o nC h e ck () //
188 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
189
193
124
204 // UNCOMMENT FOR PART 10
205 // for ( int i =0; i <3; i ++) {
206 // setX (& snake . body [ i ] , 5) ;
207 // setY (& snake . body [ i ] , 3 - i ) ;
208 // }
209 // snake . length = 3;
210 // snake . direction = left ;
211
212 uint8_t food = 0; // food in top right corner of the game board
213
219 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
220 // INSERT BUTTON PRESS DETECTION LOGIC HERE //
221 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
222
125
238 drawBuffer () ;
239 while (1) ; // Infinite loop since game is over .
240 }
241
4 void setup () {
5 timerSetup () ;
6
12 setupDisplay () ;
13 eraseBuffer ( screen_buffer ) ;
14 drawBuffer () ;
15 }
16
17 void app_main () {
126
18 setup () ;
19
50 // drawBuffer () ;
51 // long start = millis () ;
52 // while ( millis () - start < 1000) ;
53
127
54 }
55 }
18 jalr x0 , 0( ra )
19
20
128
24 pinWrite :
25 lui a2 , 0 x60004
26 addi a2 , a2 , 0 x4 # GPIO_OUT_ADDR
27
30 jalr x0 , 0( ra )
31
32
40 lui a3 , 0 x60004
41 addi a3 , a3 , 0 x20 # GPIO_ENABLE_ADDR
42
45 jalr x0 , 0( ra )
46
47
54 jalr x0 , 0( ra )
55
56
129
59 # RETURNS : Nothing
60
61 eraseBuffer :
62 addi a1 , x0 , 8 # upper bound on for loop
63 addi a2 , x0 , 0 # " i " for for loop
64 looping :
65 slli a3 , a2 , 2 # calculate 4* i
66 add a4 , a0 , a3 # get address of array element by adding
base address + 4* i
67 sw zero , 0( a4 ) # write 0 to memory address
68 addi a2 , a2 , 1 # increment i
69 bne a2 , a1 , looping # continue looping if i < 8
70 jalr x0 , 0( ra ) # return from eraseBuffer
8 void setup () {
9 timerSetup () ;
10 setupDisplay () ;
11 eraseBuffer ( screen_buffer ) ;
12 drawBuffer () ;
13 }
14
15
130
18 for ( int i = 0; i < 8; i += 1) {
19 val_for_buf = 0;
20 val = arr_to_viz [ i ];
21 for ( int j = 0; j < val ; j +=1) {
22 val_for_buf |= (1 << j ) ;
23 }
24 screen_buffer [ i ] = val_for_buf ;
25 }
26 drawBuffer () ;
27 eraseBuffer ( screen_buffer ) ;
28 long start = millis () ;
29 while ( millis () - start < 50) ;
30 }
31
34 void app_main () {
35
36 setup () ;
37 srand (13) ;
38
39 while (1) {
40 arrayViz ( testArr ) ;
41 long start = millis () ;
42 while ( millis () - start < 2000) ;
43 bubblesort ( testArr , 8) ;
44
45 // Restarts automatically
46 testArr [0] = rand () % 32;
47 testArr [1] = rand () % 32;
48 testArr [2] = rand () % 32;
49 testArr [3] = rand () % 32;
50 testArr [4] = rand () % 32;
51 testArr [5] = rand () % 32;
52 testArr [6] = rand () % 32;
53 testArr [7] = rand () % 32;
131
54
55 start = millis () ;
56 while ( millis () - start < 2000) ;
57 }
58 }
1 . section . text
2 . align 2
3 . globl bubblesort
4
5 bubblesort :
6 # a0 : first memory address of array ( DO NOT MODIFY )
7 # a1 : length of array
8
11 jalr x0 , 0( ra )
132
12 gb [4] = 0 x5 << 14;
13 gb [5] = 0 x1 << 15;
14 gb [6] = 0;
15 gb [7] = 0;
16 }
17
47 void app_main () {
133
48 timerSetup () ;
49 setupRandom () ;
50 setupDisplay () ;
51 eraseBuffer ( screen_buffer ) ;
52 const int TIME = 100; // loop time .
53 // Choose an initializer :
54 initializeLoaf ( screen_buffer ) ;
55 // initializeGlider ( screen_buffer ) ;
56 // initializeRandom ( screen_buffer ) ;
57 while (1) {
58 long start = millis () ;
59 while ( millis () - start < TIME ) ; // wait for TIME milliseconds
60 drawBuffer () ;
61 updateBoard ( screen_buffer , temp_buffer ) ;
62
1 . section . text
2 . align 2
3 . globl updateBoard
4
5 # updateBoard
6 # ARGUMENTS a0 : screen buffer ( current board ) , a1 : temporary output
buffer ( for new board )
7 # RETURNS : Nothing
8 updateBoard :
9
134
11 # Make sure to follow RISC - V calling convention !
12
13 ret
14
15 # getPixel
16 # ARGUMENTS a0 : screen_buffer , a1 : x , a2 : y
17 # RETURNS : 1 if cell occupied , 0 otherwise
18 getPixel :
19 # your code here
20 slli a2 , a2 , 2 # a2 : 4 * y for address
21 add a2 , a2 , a0 # a2 : screen_buffer + 4* y
22 lw a3 , 0( a2 ) # a3 : screen_buffer [ y ]
23 srl a3 , a3 , a1 # a3 : screen_buffer [ y ] >> _l x
24 andi a0 , a3 , 1 # a0 : a3 & 1
25 ret
26
27 # checkNeighbors
28 # ARGUMENTS a0 : game_board , a1 : x index , a2 : y index
29 # RETURNS : total occupied cells in the eight surrounding cells of (x
, y ) ( game board wraps in x and y )
30 checkNeighbors :
31 # TODO : Determine left , right , up , and down indices for
neighbors .
32
37 ret
38
39 # tallyNeighbors
40 # ARGUMENTS a0 : game_board , a1 : current x index , a2 : current y index
, a3 : left neighbor index ,
41 # a4 : right neighbor index , a5 : up neighbor index , a6 : down neighbor
index
135
42 # RETURNS : total occupied cells in the eight surrounding cells of
current (x , y )
43 tallyNeighbors :
44 # TODO : This procedure is functionally correct , but doesn ’ t
follow RISC - V calling convention .
45 # Make this procedure follow calling convention . You may only
add instructions that :
46 # 1. Increment / decrement the stack pointer
47 # 2. Put elements on the stack
48 # 3. Take elements off the stack .
49
50 mv s0 , a0 # s0 : game_board
51 mv s1 , a1 # a1 : x
52 mv s2 , a2 # a2 : y
53 li s3 , 0 # s3 : tally
54
55 mv a1 , a4
56 mv a2 , a5
57 call getPixel # getPixel ( game_board , x -1 , y -1)
58 add s3 , s3 , a0
59
60 mv a0 , s0
61 mv a1 , a4
62 mv a2 , s2
63 call getPixel # getPixel ( game_board , x -1 , y )
64 add s3 , s3 , a0
65
66 mv a0 , s0
67 mv a1 , a4
68 mv a2 , a6
69 call getPixel # getPixel ( game_board , x -1 , y +1)
70 add s3 , s3 , a0
71
72 mv a0 , s0
73 mv a1 , s1
74 mv a2 , a5
136
75 call getPixel # getPixel ( game_board , x , y -1)
76 add s3 , s3 , a0
77
78 mv a0 , s0
79 mv a1 , s1
80 mv a2 , a6
81 call getPixel # getPixel ( game_board , x , y +1)
82 add s3 , s3 , a0
83
84 mv a0 , s0
85 mv a1 , a3
86 mv a2 , a5
87 call getPixel # getPixel ( game_board , x +1 , y -1)
88 add s3 , s3 , a0
89
90 mv a0 , s0
91 mv a1 , a3
92 mv a2 , s2
93 call getPixel # getPixel ( game_board , x +1 , y )
94 add s3 , s3 , a0
95
96 mv a0 , s0
97 mv a1 , a3
98 mv a2 , a6
99 call getPixel # getPixel ( game_board , x +1 , y +1)
100 add s3 , s3 , a0
101
102 mv a0 , s3
103
104 ret
137
1 # include < stdio .h >
2 # include " 6190. h "
3
6 void setup () {
7 timerSetup () ;
8 setupDisplay () ;
9 eraseBuffer ( screen_buffer ) ;
10 drawBuffer () ;
11 }
12
13
32 void app_main () {
33
34 setup () ;
35 srand (60004) ;
36
138
37 while (1) {
38 arrayViz ( testArr ) ;
39 long start = millis () ;
40 while ( millis () - start < 2000) ;
41 quicksort ( testArr , 0 , 7) ;
42
43 // Restarts automatically
44 testArr [0] = rand () % 32;
45 testArr [1] = rand () % 32;
46 testArr [2] = rand () % 32;
47 testArr [3] = rand () % 32;
48 testArr [4] = rand () % 32;
49 testArr [5] = rand () % 32;
50 testArr [6] = rand () % 32;
51 testArr [7] = rand () % 32;
52
53 start = millis () ;
54 while ( millis () - start < 2000) ;
55 }
56 }
1 . section . text
2 . align 2
3 . globl quicksort
4
5 quicksort :
6
7 ret
8
10 partition :
11
12 ret
139
140
Bibliography
141
[14] “Calculating instructional units.” https://registrar.mit.edu/sites/
default/files/2018-12/calculating_instructional_units.pdf. Accessed:
2023-05-06.
142