Finished course
This commit is contained in:
commit
7f07304ba5
Binary file not shown.
After Width: | Height: | Size: 346 KiB |
|
@ -0,0 +1 @@
|
|||
ex2/cache_sim
|
|
@ -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.
|
|
@ -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
|
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
|
|
@ -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"));
|
||||
}
|
|
@ -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
|
|
@ -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
|
Binary file not shown.
|
@ -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;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
|||
I 8cda3fa8
|
||||
I 8158bf94
|
||||
D 8cd94c50
|
||||
I 8cd94d64
|
||||
D 8cd94c54
|
File diff suppressed because it is too large
Load Diff
|
@ -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()
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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'";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -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
|
Binary file not shown.
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue