Finished course

main
Oystein Kristoffer Tveit 2022-12-19 18:06:38 +01:00
commit 7f07304ba5
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
22 changed files with 2933005 additions and 0 deletions

BIN
.arm_cheat_sheet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
ex2/cache_sim

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 h7x4
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

52
README.md Normal file
View File

@ -0,0 +1,52 @@
# TDT4258 - Lowlevel Programming
[Course link][tdt4258]
## Exercise 1
For this exercise, we were tasked with writing a palindrome finder in assembly.
The code should be able to run on a [DE1-SoC][de1soc] emulator, that can be found at [CPUlator][cpulator].
### ARM Assembly Resources
![ARM Assembly cheat sheet](./.arm_cheat_sheet.png)
Source: [Azeria Labs][azeria-labs-cheat-sheet]
[StackOverflow - What are assembler section directives used for?][assembler-section-directives]
## Exercise 2 - Cache Simulator
In this exercise, we were tasked with writing a cache simulator.
This should run on almost any machine with a proper C compiler.
There is a Makefile and a python script included, which builds both release and debug versions of the program, and tests it with some hardcoded values.
Run the tests by executing:
```command
user@<...>/ex2 $ make test
```
### Haskell variant
I tried writing a haskell clone of this program just for fun, but I ended up never finishing it.
The code exists within a separate folder.
## Exercise 3 - Tetris on Raspberry Pi
NOTE: You will need a Raspberry Pi with a [sense hat][sense-hat] to run this code.
For this exercise, we were tasked with making an existing tetris program run on a sense hat, by writing directly to a Linux framebuffer, and reading joystick input from the Linux input driver.
Move both the Makefile and the code over to the raspberry pi before running.
[tdt4258]: https://www.ntnu.edu/studies/courses/TDT4258
[de1soc]: https://ftp.intel.com/Public/Pub/fpgaup/pub/Intel_Material/18.1/Computer_Systems/DE1-SoC/DE1-SoC_Computer_ARM.pdf
[cpulator]: https://cpulator.01xz.net/?sys=arm-de1soc
[azeria-labs-cheat-sheet]: https://azeria-labs.com/assembly-basics-cheatsheet/
[sense-hat]: https://www.raspberrypi.com/products/sense-hat/
[assembler-section-directives]: https://stackoverflow.com/questions/55107587/what-are-assembler-section-directives-used-for

BIN
ex1/DE1-SoC Manual.pdf Normal file

Binary file not shown.

BIN
ex1/assignment.pdf Normal file

Binary file not shown.

200
ex1/palin_finder.s Normal file
View File

@ -0,0 +1,200 @@
// Convention:
// R0 / R1 is method input. Should not be clobbered unless specified otherwise
// R2 - R5 + R8 - R10 is general purpose.
// R6 is the result
// Please note that this will clobber the R6 register.
// To make cpulator ignore this, turn off "Function clobbered caller-saved register" in settings.
.global _start
.section .data // RW Memory
// This is the input you are supposed to check for a palindrome
// You can modify the string during development, however you
// are not allowed to change the label 'input'!
input: .asciz "level"
// input: .asciz "8448"
// input: .asciz "KayAk"
// input: .asciz "step on no pets"
// input: .asciz "Never odd or even"
.align
.text // RO Memory
notAPalindrome: .asciz "Not a palindrome"
palindromeDetected: .asciz "Palindrome detected"
.align
// ------
LED_BASE = 0xff200000
UART_BASE = 0xff201000
// ------
// Returns a pointer to the end of the string
findStringEnd:
MOV R6, R0
MOV R0, R0
fse_loop:
LDRB R5, [R6]
CMP R5, #0x0
BXEQ LR
ADD R6, R6, #1
B fse_loop
// Checks whether the character is valid. Returns 0 or 1
isValidCharacter:
CMP R0, #'A'
BLT ivc_subrange2
CMP R0, #'Z'
BLE ivc_validChar
ivc_subrange2:
CMP R0, #'a'
BLT ivc_subrange3
CMP R0, #'z'
BLE ivc_validChar
ivc_subrange3:
CMP R0, #'0'
BLT ivc_invalidChar
CMP R0, #'9'
BLE ivc_validChar
ivc_invalidChar:
MOV R6, #0
BX LR
ivc_validChar:
MOV R6, #1
BX LR
// Makes the letter lowercase if possible
lowercaseCharacter:
MOV R6, R0
CMP R6, #'A'
BXLT LR
CMP R6, #'Z'
BXGT LR
ADD R6, #0x20
BX LR
isPalindrome:
// R0 is string start
// R1 is string length
PUSH {R0, R1}
// R2 is string pointer moving from start
// R3 is string pointer moving from end
MOV R2, R0
MOV R3, R1
// R8 stores valid lowercased start character for later comparison
// R9 stores valid lowercased end character for later comparison
ip_loop:
CMP R2, R3
BGT ip_returnTrue
// Loop until start char is valid,
// lowercase the letter, and save result in R8
LDRB R0, [R2]
BL isValidCharacter
CMP R6, #1
ADDNE R2, R2, #1
BNE ip_loop
BL lowercaseCharacter
MOV R8, R6
// Same for end char, save in R9
LDRB R0, [R3]
BL isValidCharacter
CMP R6, #1
SUBNE R3, R3, #1
BNE ip_loop
BL lowercaseCharacter
MOV R9, R6
// Compare, potentially return false
CMP R8, R9
BNE ip_returnFalse
// Move both pointers and loop
ADD R2, R2, #1
SUB R3, R3, #1
B ip_loop
ip_returnFalse:
POP {R0, R1}
MOV R6, #0
B evaluatePalindromeResult
ip_returnTrue:
POP {R0, R1}
MOV R6, #1
B evaluatePalindromeResult
_start:
BL resetLEDs
LDR R0, =input
BL findStringEnd
MOV R1, R6
// Palindromes can not be less that two chars
SUB R2, R1, R0
CMP R2, #2
BLT onNotPalindrome
B isPalindrome
evaluatePalindromeResult:
CMP R6, #1
BLEQ onPalindrome
BL onNotPalindrome
resetLEDs:
LDR R8, =LED_BASE
MOV R5, #0b0000000000
STR R5, [R8]
BX LR
onPalindrome:
// Light up LEDS
LDR R8, =LED_BASE
MOV R5, #0b0000011111
STR R5, [R8]
// Write to UART
LDR R8, =UART_BASE
LDR R9, =palindromeDetected
op_write_uart_loop:
LDRB R0, [R9]
CMP R0, #0x0
BEQ _end
STR R0, [R8]
ADD R9, R9, #1
B op_write_uart_loop
onNotPalindrome:
// Light up LEDS
LDR R8, =LED_BASE
MOV R5, #0b1111100000
STR R5, [R8]
// Write to UART
LDR R8, =UART_BASE
LDR R9, =notAPalindrome
onp_write_uart_loop:
LDRB R0, [R9]
CMP R0, #0x0
BEQ _end
STR R0, [R8]
ADD R9, R9, #1
B onp_write_uart_loop
_end:
B _end
.end

29
ex1/palindrome.c Normal file
View File

@ -0,0 +1,29 @@
#include <stdio.h>
int isValidCharacter(char character)
{
return ('a' <= character && character <= 'z')
|| ('A' <= character && character <= 'Z')
|| ('0' <= character && character <= '9');
}
int isPalindrome(int stringlen, char* string)
{
int m = 0;
int n = stringlen - 1;
while (m <= n) {
if (!isValidCharacter(string[m])) m++;
else if (!isValidCharacter(string[n])) n--;
else if (string[m] != string[n]) return 0;
else { m++; n--; }
}
return 1;
}
int main(int argc, char* argv[])
{
printf("Palindrome 1 (1): %d\n", isPalindrome(4, "abba"));
}

28
ex1/test.s Normal file
View File

@ -0,0 +1,28 @@
.global _start
loop:
// This example iteratively multiplies r2*r1
cmp r0, #0 // Compare r0 to 0
bne .+8 // If not equal branch OVER next instruction
bx lr // Branch back to who called loop
add r2, r2, r1 // Add r1 to r2 and put it on r2
sub r0, r0, #1 // Substract 1 from r0 and put it on r0
b loop // branch back to beginning of loop
_start:
// Here your execution starts
mov r0, #10 // Decimal 10 o register r0
mov r1, #2 // Decimal 2 to register r1
mov r2, #0 // Decimal 0 to register r2
bl loop // Branch and link to loop
b _exit
_exit:
// Branch to itelf
b .
.data
.align
// This section is evaluated before execution to put things into
// memory that are required for the execution of your application
.end

15
ex2/Makefile Normal file
View File

@ -0,0 +1,15 @@
.DEFAULT_GOAL := run
copyMemtrace:
rm mem_trace.txt
cp mem_trace2.txt mem_trace.txt
install: copyMemtrace
gcc cache_sim.c -o cache_sim
chmod +x cache_sim
run: install
./cache_sim 2048 dm sc
test: install
python test.py

BIN
ex2/assignment.pdf Normal file

Binary file not shown.

301
ex2/cache_sim.c Normal file
View File

@ -0,0 +1,301 @@
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#define RED_START "\033[31m"
#define COLOR_END "\033[0m"
#define DEBUG
typedef enum { dm, fa } cache_map_t;
typedef enum { uc, sc } cache_org_t;
typedef enum { instruction, data } access_t;
typedef struct {
uint32_t address;
access_t accesstype;
} mem_access_t;
typedef struct {
uint64_t accesses;
uint64_t hits;
// You can declare additional statistics if
// you like, however you are now allowed to
// remove the accesses or hits
} cache_stat_t;
// DECLARE CACHES AND COUNTERS FOR THE STATS HERE
typedef struct {
bool is_initialized;
int content;
} cache_block_t;
cache_block_t* cache;
uint32_t cache_size;
uint32_t block_size = 64;
cache_map_t cache_mapping;
cache_org_t cache_org;
// Misc
// USE THIS FOR YOUR CACHE STATISTICS
cache_stat_t cache_statistics;
/* Reads a memory access from the trace file and returns
* 1) access type (instruction or data access
* 2) memory address
*/
mem_access_t read_transaction(FILE* ptr_file) {
char buf[1000];
char* token;
char* string = buf;
mem_access_t access;
if (fgets(buf, 1000, ptr_file) != NULL) {
/* Get the access type */
token = strsep(&string, " \n");
if (strcmp(token, "I") == 0) {
access.accesstype = instruction;
} else if (strcmp(token, "D") == 0) {
access.accesstype = data;
} else {
printf(
"Could not parse access type:\n"
"%s%s%s %s\n",
RED_START, token, COLOR_END, strsep(&string, " \n"));
exit(0);
}
/* Get the access type */
token = strsep(&string, " \n");
access.address = (uint32_t)strtol(token, NULL, 16);
return access;
}
/* If there are no more entries in the file,
* return an address 0 that will terminate the infinite loop in main
*/
access.address = 0;
return access;
}
static int cache_access(mem_access_t access) {
}
const char* usage =
"Usage:\n"
"cache_sim size mapping organization [file]\n"
"\tsize: 128-4096\n"
"\tmapping: dm | fa \n"
"\torganization: uc | sc\n"
"\tfile: path\n";
/* Read command-line parameters and initialize:
* cache_size, cache_mapping and cache_org variables
*/
static void handle_arguments(int argc, char** argv) {
if (argc < 4) perror(usage);
/* argv[0] is program name, parameters start with argv[1] */
/* Set cache size */
cache_size = atoi(argv[1]);
// if (128 < cache_size || cache_size < 4096) error(
// "Cache size needs to be be between 128 and 4096\n"
// "Please check the input: %s%s%s",
// RED_START, argv[1], COLOR_END
// );
/* Set Cache Mapping */
if (strcmp(argv[2], "dm") == 0) {
cache_mapping = dm;
} else if (strcmp(argv[2], "fa") == 0) {
cache_mapping = fa;
} else {
printf("Unknown cache mapping\n");
exit(1);
}
/* Set Cache Organization */
if (strcmp(argv[3], "uc") == 0) {
cache_org = uc;
} else if (strcmp(argv[3], "sc") == 0) {
cache_org = sc;
} else {
printf("Unknown cache organization\n");
exit(1);
}
}
static bool access_cache_dm(int index, int tag) {
bool foundMatch = cache[index].is_initialized
&& cache[index].content == tag;
if (foundMatch) return true;
cache[index].is_initialized = true;
cache[index].content = tag;
#ifdef DEBUG
// printf("Overwriting tag at cache[%d] = %x\n", index, cache + index);
#endif
return false;
}
static bool access_cache_fa(int tag, cache_block_t* local_cache, int* counter, int number_of_blocks) {
for (int i = 0; i < number_of_blocks; i++)
if (local_cache[i].is_initialized && local_cache[i].content == tag)
return true;
(*counter)++;
// if (*counter == number_of_blocks) *counter = 0;
(*counter) %= number_of_blocks;
local_cache[*counter].is_initialized = true;
local_cache[*counter].content = tag;
#ifdef DEBUG
// printf("Overwriting tag at cache[%d] = %x\n", *counter, local_cache + *counter);
#endif
return false;
}
int main(int argc, char** argv) {
// Reset statistics:
memset(&cache_statistics, 0, sizeof(cache_stat_t));
handle_arguments(argc, argv);
// Will truncate 0, but that should not matter if args are handled correctly.
int number_of_blocks = cache_size / block_size;
int bits_for_offset = 0;
int bs = block_size;
while (bs % 2 == 0) {
bits_for_offset++;
bs = bs >> 1;
}
int bits_for_index = 0;
if (cache_mapping == dm) {
int nob = number_of_blocks;
while (nob % 2 == 0) {
bits_for_index++;
nob = nob >> 1;
}
// If the cache is half the size (split), we will need 1 less bit for the index.
if (cache_org == sc) bits_for_index --;
}
int bits_for_tag = 32 - bits_for_index - bits_for_offset;
int offset_mask = (1 << bits_for_offset) - 1;
int index_mask = ((1 << bits_for_index) - 1) << bits_for_offset;
int tag_mask = ((1 << bits_for_tag) - 1) << (bits_for_index + bits_for_index);
#ifdef DEBUG
printf("Block offset mask: %08x\n", offset_mask);
printf("Index mask: %08x\n", index_mask);
printf("Tag mask: %08x\n", tag_mask);
#endif
int fa_counter1 = 0;
int fa_counter2 = 0;
int* fa_counter1_p = &fa_counter1;
int* fa_counter2_p = &fa_counter2;
#ifdef DEBUG
printf("Number of blocks: %d\n", number_of_blocks);
printf("Bits for offset: %d\n", bits_for_offset);
printf("Bits for index: %d\n", bits_for_index);
printf("Bits for tag: %d\n", bits_for_tag);
printf("Size of cache_block: %d\n", sizeof(cache_block_t));
printf("Cache size: %d\n", number_of_blocks * sizeof(cache_block_t));
printf("Half cache size: %d\n", number_of_blocks * sizeof(cache_block_t) / 2);
#endif
cache = (cache_block_t*) malloc(number_of_blocks * sizeof(cache_block_t));
// Pointer arithmetic black magic compiler voodoo will ensure that this is correct
// (I spent waaaay to long figuring out that '+' doesn't literally mean integer addition when working
// with pointers types...)
cache_block_t* upper_half_cache = cache + (number_of_blocks / 2);
#ifdef DEBUG
printf("Cache location: %d\n", cache);
printf("Upper half cache location: %d\n\n", upper_half_cache);
#endif
// exit(1);
// The global cache memory and global args values should now have been set.
/* Open the file mem_trace.txt to read memory accesses */
FILE* ptr_file;
ptr_file = fopen("mem_trace.txt", "r");
if (!ptr_file) {
printf("Unable to open the trace file\n");
exit(1);
}
#ifdef DEBUG
int line = 0;
#endif
/* Loop until whole trace file has been read */
mem_access_t access;
while (1) {
access = read_transaction(ptr_file);
// If no transactions left, break out of loop
if (access.address == 0) break;
/* Do a cache access */
cache_statistics.accesses++;
int tag = (access.address & tag_mask) >> bits_for_offset + bits_for_index;
int index = (access.address & index_mask) >> bits_for_offset;
bool is_data = access.accesstype == data;
#ifdef DEBUG
// printf("Line: %u\n", line++);
// printf("%c %08x\n", access.accesstype == instruction ? 'I' : 'D', access.address);
// printf("Tag: %x\n", tag);
// printf("Index: %x\n", index);
#endif
if ( (cache_mapping == fa && cache_org == uc && access_cache_fa(tag, cache, fa_counter1_p, number_of_blocks))
|| (cache_mapping == fa && cache_org == sc && (!is_data
? access_cache_fa(tag, cache, fa_counter1_p, number_of_blocks / 2)
: access_cache_fa(tag, upper_half_cache, fa_counter2_p, number_of_blocks / 2)))
|| (cache_mapping == dm && cache_org == uc && access_cache_dm(index, tag))
|| (cache_mapping == dm && cache_org == sc && access_cache_dm(index + (number_of_blocks / 2) * is_data, tag))
) {
#ifdef DEBUG
// printf("Hit!\n");
#endif
cache_statistics.hits++;
}
#ifdef DEBUG
// printf("\n");
#endif
}
printf("\nCache Statistics\n");
printf("-----------------\n\n");
printf("Accesses: %ld\n", cache_statistics.accesses);
printf("Hits: %ld\n", cache_statistics.hits);
printf("Hit Rate: %.4f\n",
(double)cache_statistics.hits / cache_statistics.accesses);
fclose(ptr_file);
return 0;
}

1465707
ex2/mem_trace.txt Normal file

File diff suppressed because it is too large Load Diff

5
ex2/mem_trace1.txt Normal file
View File

@ -0,0 +1,5 @@
I 8cda3fa8
I 8158bf94
D 8cd94c50
I 8cd94d64
D 8cd94c54

1465707
ex2/mem_trace2.txt Normal file

File diff suppressed because it is too large Load Diff

31
ex2/test.py Normal file
View File

@ -0,0 +1,31 @@
import subprocess
green = '\033[32m'
red = '\033[31m'
clr = '\033[0m'
expected = {
"2048 dm uc": 0.8903,
"1024 dm uc": 0.8731,
"2048 dm sc": 0.9104,
"1024 dm sc": 0.8903,
"2048 fa uc": 0.9174,
"1024 fa uc": 0.8918,
"2048 fa sc": 0.9167,
"1024 fa sc": 0.8955,
}
for (args, ex) in expected.items():
try:
command = ['./cache_sim'] + args.split(' ') + [ ]
sim = subprocess.Popen(command, stdout=subprocess.PIPE)
sim.wait()
subprocess.check_call(('grep', f'Hit Rate: {ex}'), stdin = sim.stdout)
print(f'[{green}V{clr}] {args}: {ex}')
except:
print(f'[{red}X{clr}] {args}: {ex}')
# Optimally, we would tee the output from the original sim here, but it's too teedious :)
sim2 = subprocess.Popen(command)
sim2.wait()
print()

200
ex2_haskell/cache_sim.hs Normal file
View File

@ -0,0 +1,200 @@
{-# LANGUAGE NamedFieldPuns #-}
import System.Exit (exitFailure)
import Data.Bits
import System.Console.GetOpt
import Text.Read (readMaybe)
import System.Environment (getArgs)
import Data.List (intersperse)
import Data.Maybe (isNothing)
import Numeric (readHex)
import Control.Exception (throw)
import Data.Array
data CacheMap = DirectMapping | FullAssociative
deriving (Show, Eq)
data CacheOrg = Unified | Split
deriving (Show, Eq)
data Config = Config { mapping :: CacheMap
, organization :: CacheOrg
, size :: Int
, file :: String
, blockSize :: Int
}
deriving (Show)
data CacheStats = CacheStats { hits :: Int
, misses :: Int
}
deriving (Show)
instance Semigroup CacheStats where
c1 <> c2 = CacheStats { hits = (hits c1) + (hits c2)
, misses = (misses c1) + (misses c2)
}
instance Monoid CacheStats where
mempty = CacheStats { hits = 0
, misses = 0
}
data MemoryAccess = Instruction Int
| Data Int
deriving (Show, Eq)
address :: MemoryAccess -> Int
address (Instruction a) = a
address (Data a) = a
data Flag = FlagMapping (Either String CacheMap)
| FlagOrganization (Either String CacheOrg )
| FlagSize (Either String Int)
| FlagFile String
| FlagHelp
deriving (Show, Eq)
-- class Cache a e where
-- contains :: a -> e -> Bool
-- insert :: a -> e -> a
-- newtype DirectMappedCache m = DirectMappedCache m
-- newtype FullAssociativeCache m = FullAssociativeCache m
-- instance Cache (DirectMappedCache Array) Int where
-- contains cache value = undefined
-- insert cache value = undefined
-- instance Cache (FullAssociativeCache Array) Int where
-- contains cache value = undefined
-- insert cache value = undefined
options :: [OptDescr Flag]
options = [ Option ['m'] ["mapping"] m "Type of cache mapping"
, Option ['o'] ["organization"] o "Type of cache organization"
, Option ['s'] ["size"] s "Cache size"
, Option ['f'] ["file"] f "File with memory dump"
, Option ['h', '?'] ["help"] (NoArg FlagHelp) "Show usage"
]
where
fm :: String -> Either String CacheMap
fm "DM" = Right DirectMapping
fm "FA" = Right FullAssociative
fm x = Left $ "No such cache mapping: " ++ x
m = ReqArg (FlagMapping . fm) "DM|FA"
fo :: String -> Either String CacheOrg
fo "UC" = Right Unified
fo "SC" = Right Split
fo x = Left $ "No such cache organization: " ++ x
o = ReqArg (FlagOrganization . fo) "UC|SC"
isPowerOf2 n = n .&. (n - 1) == 0
fs :: String -> Either String Int
fs x = case readMaybe x of
Nothing -> Left $ "Cannot parse cache size: " ++ x
Just i -> if isPowerOf2 i
then Right i
else Left $ x ++ " is not a power of 2"
s = ReqArg (FlagSize . fs) "KB (number which is a power of 2)"
f = ReqArg FlagFile "FILE"
handleArgs :: IO (Either String Config)
handleArgs = do
argList <- getArgs
let args = getOpt RequireOrder options argList
-- TODO: Print Flag Left sides
return $ case args of
(_,_,errs@(_:_)) -> Left $ concat errs ++ usageInfo "" options
(FlagMapping (Right m):FlagOrganization (Right o):FlagSize (Right s):FlagFile f:_,[],[]) ->
Right $ Config { mapping = m
, organization = o
, size = s
, file = f
, blockSize = 64
}
_ -> Left $ usageInfo "aaaaa" options
parseFile :: String -> Either String [MemoryAccess]
parseFile = mapM lineToInstr . lines
where
lineToInstr :: String -> Either String MemoryAccess
lineToInstr s = do
(i,d) <- case words s of
i:d:_ -> Right (i, d)
_ -> Left $ "Cannot parse line: " ++ s
n <- case readHex d of
[(n,"")] -> Right n
_ -> Left $ "Cannot parse line: " ++ s
case i of
"I" -> Right $ Instruction n
"D" -> Right $ Data n
_ -> Left $ "Cannot parse line: " ++ s
mask :: Int -> Int -> Int -> Int
mask digits offset n = shift (2^digits - 1) offset .&. n
directMappingSimulation :: Config -> [MemoryAccess] -> t2 -> CacheStats -> CacheStats
directMappingSimulation c [] cache stats = stats
directMappingSimulation c (m:ms) cache stats = let
numberOfBlocks = (size c) `div` (blockSize c)
bitsForOffset = finiteBitSize $ blockSize c
bitsForIndex = finiteBitSize $ numberOfBlocks
bitsForTag = 32 - bitsForIndex - bitsForOffset
index = mask bitsForIndex bitsForOffset $ address m
tag = mask bitsForTag (bitsForIndex + bitsForOffset) $ address m
contains = undefined
newCache = if contains m cache
then cache
else
newStats = if contains m cache
then stats { hits = hits stats + 1 }
else stats { misses = misses stats + 1 }
in directMappingSimulation c ms newCache newStats
fullAssociativeSimulation :: Config -> [MemoryAccess] -> t2 -> Int -> CacheStats -> CacheStats
fullAssociativeSimulation c [] cache i stats = stats
fullAssociativeSimulation c (m:ms) cache i stats = let
tag = mask (32 - (finiteBitSize $ blockSize c)) (finiteBitSize $ blockSize c) $ address m
contains = undefined
newCache = undefined
newStats = if contains m cache i
then stats { hits = hits stats + 1 }
else stats { misses = misses stats + 1 }
in fullAssociativeSimulation c ms newCache (i + 1 `mod` (blockSize c)) newStats
-- f :: [MemoryAccess] -> Writer CacheStats [MemoryAccess]
-- f accessesLeft = x
-- where
-- head x
simulateCache :: Config -> [MemoryAccess] -> CacheStats
simulateCache c m =
CacheStats {hits=1, misses=2}
printStats :: CacheStats -> IO ()
printStats = print
main :: IO ()
main = do
config <- handleArgs
config' <- case config of
Left err -> putStrLn err >> exitFailure
Right cfg -> return cfg
fileContent <- readFile $ file config'
print $ parseFile fileContent
case parseFile fileContent of
Left err -> putStrLn err >> exitFailure
Right memory -> printStats $ simulateCache config' memory

26
ex2_haskell/flake.lock Normal file
View File

@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1664029467,
"narHash": "sha256-ir7JbsLp2mqseCs3qI+Z/pkt+Gh+GfANbYcI5I+Gvnk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "893b6b9f6c4ed0c7efdb84bd300a499a2da9fa51",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-22.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

21
ex2_haskell/flake.nix Normal file
View File

@ -0,0 +1,21 @@
{
description = "My haskell project";
inputs.nixpkgs.url = "nixpkgs/nixos-22.05";
outputs = { self, nixpkgs }: let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in {
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
ghc
ghcid
haskell-language-server
hlint
];
shellHook = "export PS1='\\e[1;34m[nix] cache_sim> \\e[0m'";
};
};
}

24
ex3/Makefile Normal file
View File

@ -0,0 +1,24 @@
GCC_RELEASE_FLAGS := -o3 # Optimisation level 3
GCC_DEBUG_FLAGS := -g \
-D_FORTIFY_SOURCE=2 \
-fasynchronous-unwind-tables \
-fstack-protector-strong \
-Wall \
-Werror=format-security \
-Werror=implicit-function-declaration
# This is stupid, but I've found no other way to comment between \ based
# newlines in Makefiles.
# -D_FORTIFY_SOURCE=2 # Detect buffer overflows during runtime
# -fasynchronous-unwind-tables # Increased readability for backtraces
# -fstack-protector-strong # Stack smashing protector
# -Wall # Compiler warnings
# -Werror=format-security # Bad string formatting and implicit calls should be errors
# -Werror=implicit-function-declaration
debug:
gcc $(GCC_DEBUG_FLAGS) stetris.c -o dtetris
release:
gcc $(GCC_RELEASE_FLAGS) stetris.c -o stetris

BIN
ex3/assignment.pdf Normal file

Binary file not shown.

637
ex3/stetris.c Normal file
View File

@ -0,0 +1,637 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <sys/select.h>
#include <linux/input.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <poll.h>
// New imports
#include <linux/fb.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/mman.h>
#include <time.h>
// The game state can be used to detect what happens on the playfield
#define GAMEOVER 0
#define ACTIVE (1 << 0)
#define ROW_CLEAR (1 << 1)
#define TILE_ADDED (1 << 2)
// First conversion to split the values of 8 bit RGB,
// then some magic bit masking stuff to convert into RGB565.
#define RGB(N) { \
.r = (((N & 0xFF0000) >> 16) >> 3) & 0x1f, \
.g = (((N & 0x00FF00) >> 8) >> 2) & 0x3f, \
.b = (((N & 0x0000FF) ) >> 3) & 0x1f, \
}
typedef struct {
uint8_t r : 5;
uint8_t g : 6;
uint8_t b : 5;
} color_t;
// If you extend this structure, either avoid pointers or adjust
// the game logic allocate/deallocate and reset the memory
typedef struct {
bool occupied;
// Added a color to every tile.
color_t color;
} tile;
typedef struct {
unsigned int x;
unsigned int y;
} coord;
typedef struct {
coord const grid; // playfield bounds
unsigned long const uSecTickTime; // tick rate
unsigned long const rowsPerLevel; // speed up after clearing rows
unsigned long const initNextGameTick; // initial value of nextGameTick
unsigned int tiles; // number of tiles played
unsigned int rows; // number of rows cleared
unsigned int score; // game score
unsigned int level; // game level
tile *rawPlayfield; // pointer to raw memory of the playfield
tile **playfield; // This is the play field array
unsigned int state;
coord activeTile; // current tile
unsigned long tick; // incremeted at tickrate, wraps at nextGameTick
// when reached 0, next game state calculated
unsigned long nextGameTick; // sets when tick is wrapping back to zero
// lowers with increasing level, never reaches 0
} gameConfig;
gameConfig game = {
.grid = {8, 8},
.uSecTickTime = 10000,
.rowsPerLevel = 2,
.initNextGameTick = 50,
};
// This is (supposed to be) monokai.
// Because of how the display works, it doesn't look correct on the sense hat,
// but at least they are clear and distinct colors.
const color_t color_scheme[] = {
RGB(0xF92672), // red
RGB(0xA6E22E), // green
RGB(0xA1EFE4), // cyan
RGB(0x66D9EF), // blue
RGB(0xAE81FF), // violet
RGB(0xFD5FF0), // magenta
RGB(0xFD971F), // orange
RGB(0xE6DB74), // yellow
};
const size_t color_scheme_n = sizeof(color_scheme) / sizeof(color_t);
const color_t black = {
.r = 0,
.g = 0,
.b = 0,
};
static inline uint16_t color_to_pixel(color_t color) {
return color.r
| color.g << 5
| color.b << 11;
}
// The setup of the LED matrix and the joystick was pretty similar,
// So I extracted the differences and made this unified procedure which
// are common for both of them.
static bool initializeDevice(char* dev_path,
char* dev_prefix,
char* sys_path,
char* dev_name,
bool (*handle_device)(char*)) {
DIR* devdir = opendir(dev_path);
if (devdir == NULL) {
fprintf(stderr, "Could not open directory '%s'...", dev_path);
return false;
}
struct dirent* file;
while ((file = readdir(devdir))) {
char* devx = file->d_name;
if (strncmp(devx, dev_prefix, strlen(dev_prefix)) != 0) continue;
// /sys/... %s dev xx
char* name_file = alloca(strlen(sys_path) - 2 + strlen(dev_prefix) + 2);
sprintf(name_file, sys_path, devx);
// read out the content of /sys/class/...../name, and compare it to the expected name
FILE* name_fd = fopen(name_file, "r");
char* content = alloca(strlen(dev_name));
fgets(content, strlen(dev_name) + 1, name_fd);
int device_name_is_matching = strncmp(content, dev_name, strlen(dev_name));
fclose(name_fd);
if (device_name_is_matching != 0) continue;
// If the device name is correct, do something interesting with it.
// If that fails, clean up the content of this proc, and forward the failure.
if (!handle_device(devx)) goto exitError;
return true;
}
fprintf(stderr, "Could not find device with name %s. Is it plugged in?\n", dev_name);
exitError:
closedir(devdir);
return false;
}
uint16_t* framebuffer = NULL;
struct fb_fix_screeninfo fixed_info;
struct fb_var_screeninfo variable_info;
int fb_size = sizeof(color_t) * 8 * 8;
// Initialization for the led matrix.
// This creates a file handler for the framebuffer device,
// memory maps it to an array of the same size,
// and then closes the file handler.
static bool led_matrix_callback(char* fbx) {
printf("Found SenseHat framebuffer: %s\n", fbx);
char devfbx[9];
sprintf(devfbx, "/dev/%s", fbx);
int framebuffer_fd = open(devfbx, O_RDWR);
if (framebuffer_fd == -1) {
fprintf(stderr, "Could not open SenseHat framebuffer...\n");
return false;
}
if ( ioctl(framebuffer_fd, FBIOGET_FSCREENINFO, &fixed_info) != 0
|| ioctl(framebuffer_fd, FBIOGET_VSCREENINFO, &variable_info) != 0
) {
fprintf(stderr, "Could not get screen info for %s...\n", devfbx);
return false;
};
// I think this will probably break if it for some reason turns out not to be 16 * 8 * 8,
// but I'll leave the line here nonetheless.
fb_size = fixed_info.line_length * variable_info.xres * variable_info.yres;
printf("Screen size: %i * %i = %i\n", variable_info.xres, variable_info.yres, fb_size);
framebuffer = (uint16_t*) mmap(0, fb_size, PROT_WRITE, MAP_SHARED, framebuffer_fd, 0);
if (framebuffer == -1) {
fprintf(stderr, "Could not create framebuffer mapping...\n");
return false;
}
close(framebuffer_fd);
return true;
}
int joystick_fd = -1;
// Initialization for the joystick
// This creates a file handler for the joystick device,
// in order for it to be continuously read during the game.
static bool joystick_callback(char* eventx) {
printf("Found SenseHat joystick: %s\n", eventx);
char deveventx[17];
sprintf(deveventx, "/dev/input/%s", eventx);
joystick_fd = open(deveventx, O_RDONLY);
if (joystick_fd == -1) {
fprintf(stderr, "Could not open SenseHat joystick...\n");
return false;
}
return true;
}
// This function is called on the start of your application
// Here you can initialize what ever you need for your task
// return false if something fails, else true
static bool initializeSenseHat() {
// Set randomness seed to current time, in order for the colors to
// become (virtually) different every time you run the program.
srand(time(0));
initializeDevice(
"/dev",
"fb",
"/sys/class/graphics/%s/name",
"RPi-Sense FB",
led_matrix_callback
);
initializeDevice(
"/dev/input",
"event",
"/sys/class/input/%s/device/name",
"Raspberry Pi Sense HAT Joystick",
joystick_callback
);
return (framebuffer != NULL && joystick_fd != -1);
}
// This function is called when the application exits
// Here you can free up everything that you might have opened/allocated
static void freeSenseHat() {
munmap(framebuffer, fb_size);
close(joystick_fd);
}
// This function should return the key that corresponds to the joystick press
// KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, with the respective direction
// and KEY_ENTER, when the the joystick is pressed
// !!! when nothing was pressed you MUST return 0 !!!
int readSenseHatJoystick() {
struct pollfd poll_fd = {
.fd = joystick_fd,
.events = POLLIN,
};
int new_input_available = poll(&poll_fd, 1, 0);
if (!new_input_available) return 0;
struct input_event event;
read(joystick_fd, &event, sizeof(event));
// If the event value is not 'playing',
// it means that the joystick was released back to the center,
// which is an event we don't really care about.
if (event.value != FF_STATUS_PLAYING) return 0;
switch (event.code) {
case KEY_UP:
case KEY_DOWN:
case KEY_LEFT:
case KEY_RIGHT:
case KEY_ENTER:
return event.code;
default:
return 0;
}
}
// This function should render the gamefield on the LED matrix. It is called
// every game tick. The parameter playfieldChanged signals whether the game logic
// has changed the playfield
static void renderSenseHatMatrix(bool const playfieldChanged) {
if (!playfieldChanged) return;
for (int i = 0; i < variable_info.xres * variable_info.yres; i++)
framebuffer[i] = color_to_pixel(game.rawPlayfield[i].occupied ? game.rawPlayfield[i].color : black);
}
// The game logic uses only the following functions to interact with the playfield.
// if you choose to change the playfield or the tile structure, you might need to
// adjust this game logic <> playfield interface
static inline void newTile(coord const target) {
game.playfield[target.y][target.x].occupied = true;
game.playfield[target.y][target.x].color = color_scheme[rand() % color_scheme_n];
}
static inline void copyTile(coord const to, coord const from) {
memcpy((void *) &game.playfield[to.y][to.x], (void *) &game.playfield[from.y][from.x], sizeof(tile));
}
static inline void copyRow(unsigned int const to, unsigned int const from) {
memcpy((void *) &game.playfield[to][0], (void *) &game.playfield[from][0], sizeof(tile) * game.grid.x);
}
static inline void resetTile(coord const target) {
memset((void *) &game.playfield[target.y][target.x], 0, sizeof(tile));
}
static inline void resetRow(unsigned int const target) {
memset((void *) &game.playfield[target][0], 0, sizeof(tile) * game.grid.x);
}
static inline bool tileOccupied(coord const target) {
return game.playfield[target.y][target.x].occupied;
}
static inline bool rowOccupied(unsigned int const target) {
for (unsigned int x = 0; x < game.grid.x; x++) {
coord const checkTile = {x, target};
if (!tileOccupied(checkTile)) {
return false;
}
}
return true;
}
static inline void resetPlayfield() {
for (unsigned int y = 0; y < game.grid.y; y++) {
resetRow(y);
}
}
// Below here comes the game logic. Keep in mind: You are not allowed to change how the game works!
// that means no changes are necessary below this line! And if you choose to change something
// keep it compatible with what was provided to you!
bool addNewTile() {
game.activeTile.y = 0;
game.activeTile.x = (game.grid.x - 1) / 2;
if (tileOccupied(game.activeTile))
return false;
newTile(game.activeTile);
return true;
}
bool moveRight() {
coord const newTile = {game.activeTile.x + 1, game.activeTile.y};
if (game.activeTile.x < (game.grid.x - 1) && !tileOccupied(newTile)) {
copyTile(newTile, game.activeTile);
resetTile(game.activeTile);
game.activeTile = newTile;
return true;
}
return false;
}
bool moveLeft() {
coord const newTile = {game.activeTile.x - 1, game.activeTile.y};
if (game.activeTile.x > 0 && !tileOccupied(newTile)) {
copyTile(newTile, game.activeTile);
resetTile(game.activeTile);
game.activeTile = newTile;
return true;
}
return false;
}
bool moveDown() {
coord const newTile = {game.activeTile.x, game.activeTile.y + 1};
if (game.activeTile.y < (game.grid.y - 1) && !tileOccupied(newTile)) {
copyTile(newTile, game.activeTile);
resetTile(game.activeTile);
game.activeTile = newTile;
return true;
}
return false;
}
bool clearRow() {
if (rowOccupied(game.grid.y - 1)) {
for (unsigned int y = game.grid.y - 1; y > 0; y--) {
copyRow(y, y - 1);
}
resetRow(0);
return true;
}
return false;
}
void advanceLevel() {
game.level++;
switch(game.nextGameTick) {
case 1:
break;
case 2 ... 10:
game.nextGameTick--;
break;
case 11 ... 20:
game.nextGameTick -= 2;
break;
default:
game.nextGameTick -= 10;
}
}
void newGame() {
game.state = ACTIVE;
game.tiles = 0;
game.rows = 0;
game.score = 0;
game.tick = 0;
game.level = 0;
resetPlayfield();
}
void gameOver() {
game.state = GAMEOVER;
game.nextGameTick = game.initNextGameTick;
}
bool sTetris(int const key) {
bool playfieldChanged = false;
if (game.state & ACTIVE) {
// Move the current tile
if (key) {
playfieldChanged = true;
switch(key) {
case KEY_LEFT:
moveLeft();
break;
case KEY_RIGHT:
moveRight();
break;
case KEY_DOWN:
while (moveDown()) {};
game.tick = 0;
break;
default:
playfieldChanged = false;
}
}
// If we have reached a tick to update the game
if (game.tick == 0) {
// We communicate the row clear and tile add over the game state
// clear these bits if they were set before
game.state &= ~(ROW_CLEAR | TILE_ADDED);
playfieldChanged = true;
// Clear row if possible
if (clearRow()) {
game.state |= ROW_CLEAR;
game.rows++;
game.score += game.level + 1;
if ((game.rows % game.rowsPerLevel) == 0) {
advanceLevel();
}
}
// if there is no current tile or we cannot move it down,
// add a new one. If not possible, game over.
if (!tileOccupied(game.activeTile) || !moveDown()) {
if (addNewTile()) {
game.state |= TILE_ADDED;
game.tiles++;
} else {
gameOver();
}
}
}
}
// Press any key to start a new game
if ((game.state == GAMEOVER) && key) {
playfieldChanged = true;
newGame();
addNewTile();
game.state |= TILE_ADDED;
game.tiles++;
}
return playfieldChanged;
}
int readKeyboard() {
struct pollfd pollStdin = {
.fd = STDIN_FILENO,
.events = POLLIN
};
int lkey = 0;
if (poll(&pollStdin, 1, 0)) {
lkey = fgetc(stdin);
if (lkey != 27)
goto exit;
lkey = fgetc(stdin);
if (lkey != 91)
goto exit;
lkey = fgetc(stdin);
}
exit:
switch (lkey) {
case 10: return KEY_ENTER;
case 65: return KEY_UP;
case 66: return KEY_DOWN;
case 67: return KEY_RIGHT;
case 68: return KEY_LEFT;
}
return 0;
}
void renderConsole(bool const playfieldChanged) {
if (!playfieldChanged)
return;
// Goto beginning of console
fprintf(stdout, "\033[%d;%dH", 0, 0);
for (unsigned int x = 0; x < game.grid.x + 2; x ++) {
fprintf(stdout, "-");
}
fprintf(stdout, "\n");
for (unsigned int y = 0; y < game.grid.y; y++) {
fprintf(stdout, "|");
for (unsigned int x = 0; x < game.grid.x; x++) {
coord const checkTile = {x, y};
fprintf(stdout, "%c", (tileOccupied(checkTile)) ? '#' : ' ');
}
switch (y) {
case 0:
fprintf(stdout, "| Tiles: %10u\n", game.tiles);
break;
case 1:
fprintf(stdout, "| Rows: %10u\n", game.rows);
break;
case 2:
fprintf(stdout, "| Score: %10u\n", game.score);
break;
case 4:
fprintf(stdout, "| Level: %10u\n", game.level);
break;
case 7:
fprintf(stdout, "| %17s\n", (game.state == GAMEOVER) ? "Game Over" : "");
break;
default:
fprintf(stdout, "|\n");
}
}
for (unsigned int x = 0; x < game.grid.x + 2; x++) {
fprintf(stdout, "-");
}
fflush(stdout);
}
inline unsigned long uSecFromTimespec(struct timespec const ts) {
return ((ts.tv_sec * 1000000) + (ts.tv_nsec / 1000));
}
int main(int argc, char **argv) {
(void) argc;
(void) argv;
// This sets the stdin in a special state where each
// keyboard press is directly flushed to the stdin and additionally
// not outputted to the stdout
{
struct termios ttystate;
tcgetattr(STDIN_FILENO, &ttystate);
ttystate.c_lflag &= ~(ICANON | ECHO);
ttystate.c_cc[VMIN] = 1;
tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
}
// Allocate the playing field structure
game.rawPlayfield = (tile *) malloc(game.grid.x * game.grid.y * sizeof(tile));
game.playfield = (tile**) malloc(game.grid.y * sizeof(tile *));
if (!game.playfield || !game.rawPlayfield) {
fprintf(stderr, "ERROR: could not allocate playfield\n");
return 1;
}
for (unsigned int y = 0; y < game.grid.y; y++) {
game.playfield[y] = &(game.rawPlayfield[y * game.grid.x]);
}
// Reset playfield to make it empty
resetPlayfield();
// Start with gameOver
gameOver();
if (!initializeSenseHat()) {
fprintf(stderr, "ERROR: could not initilize sense hat\n");
return 1;
};
// Clear console, render first time
fprintf(stdout, "\033[H\033[J");
renderConsole(true);
renderSenseHatMatrix(true);
while (true) {
struct timeval sTv, eTv;
gettimeofday(&sTv, NULL);
int key = readSenseHatJoystick();
if (!key)
key = readKeyboard();
if (key == KEY_ENTER)
break;
bool playfieldChanged = sTetris(key);
renderConsole(playfieldChanged);
renderSenseHatMatrix(playfieldChanged);
// Wait for next tick
gettimeofday(&eTv, NULL);
unsigned long const uSecProcessTime = ((eTv.tv_sec * 1000000) + eTv.tv_usec) - ((sTv.tv_sec * 1000000 + sTv.tv_usec));
if (uSecProcessTime < game.uSecTickTime) {
usleep(game.uSecTickTime - uSecProcessTime);
}
game.tick = (game.tick + 1) % game.nextGameTick;
}
freeSenseHat();
free(game.playfield);
free(game.rawPlayfield);
return 0;
}