diff --git a/.gitignore b/.gitignore
index 1d597b3..5ba1d71 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 .vscode/
 build/
-programs/tests/
\ No newline at end of file
+programs/tests/
+toolchain/
\ No newline at end of file
diff --git a/README.md b/README.md
index 0795c4a..46ecb6f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-RISC-V (rv32mi) emulator in C \
+RISC-V (rv32i + Zicsr) emulator in C \
 This is just for me to understand how all this works, and to learn something new. \
 So don't use it.
 
diff --git a/include/address_space.h b/include/address_space.h
index 3f7df9f..f8f7980 100644
--- a/include/address_space.h
+++ b/include/address_space.h
@@ -16,6 +16,9 @@ struct AddressSpace_s {
     uint8_t *ram;
     // The size of RAM.
     uint32_t ramSize;
+
+    // The CSRs, size is always 4096 (12 bits)
+    uint32_t *csr;
 };
 
 typedef struct AddressSpace_s AddressSpace;
@@ -23,5 +26,7 @@ typedef struct AddressSpace_s AddressSpace;
 AddressSpace *create_address_space(const uint32_t romSize, const uint32_t ramSize);
 int read_address_space(const AddressSpace *addressSpace, const uint32_t address, const int n, void *dest);
 int write_address_space(const AddressSpace *addressSpace, const uint32_t address, const int n, void *src);
+int read_csr(const AddressSpace *addressSpace, const uint16_t address, void *dest);
+int write_csr(const AddressSpace *addressSpace, const uint16_t address, void *src);
 
 #endif
\ No newline at end of file
diff --git a/programs/fib.elf b/programs/fib.elf
new file mode 100755
index 0000000..c6eb92e
Binary files /dev/null and b/programs/fib.elf differ
diff --git a/programs/src/fib.c b/programs/src/fib.c
new file mode 100644
index 0000000..51ec695
--- /dev/null
+++ b/programs/src/fib.c
@@ -0,0 +1,14 @@
+unsigned int main() {
+    int n = 47;
+
+    if (n <= 1) return n;
+    
+    unsigned int prev = 0, curr = 1, next;
+    for (int i = 2; i <= n; i++) {
+        next = prev + curr;
+        prev = curr;
+        curr = next;
+    }
+
+    return curr;
+}
\ No newline at end of file
diff --git a/src/address_space.c b/src/address_space.c
index f0646e4..cc75af1 100644
--- a/src/address_space.c
+++ b/src/address_space.c
@@ -23,6 +23,7 @@ AddressSpace *create_address_space(const uint32_t romSize, const uint32_t ramSiz
     addressSpace->romLocked = false;
     addressSpace->ram = ram;
     addressSpace->ramSize = ramSize;
+    addressSpace->csr = calloc(4096, 1);
 
     return addressSpace;
 }
@@ -82,5 +83,27 @@ int write_address_space(const AddressSpace *addressSpace, const uint32_t address
         return 1;
     }
 
+    return 0;
+}
+
+int read_csr(const AddressSpace *addressSpace, const uint16_t address, void *dest) {
+    if (address >= 4096) {
+        fprintf(stderr, "Trying to read from CSR address 0x%X, which is higher than maximum 0xFFF\n", address);
+        return 1;
+    }
+
+    memcpy(dest, addressSpace->csr + address, 4);
+
+    return 0;
+}
+
+int write_csr(const AddressSpace *addressSpace, const uint16_t address, void *dest) {
+    if (address >= 4096) {
+        fprintf(stderr, "Trying to write to CSR address 0x%X, which is higher than maximum 0xFFF\n", address);
+        return 1;
+    }
+
+    memcpy(addressSpace->csr + address, dest, 4);
+
     return 0;
 }
\ No newline at end of file
diff --git a/src/instruction_executor.c b/src/instruction_executor.c
index 493a6b4..b47701d 100644
--- a/src/instruction_executor.c
+++ b/src/instruction_executor.c
@@ -238,11 +238,11 @@ int execute_instruction_on_cpu(CPU *cpu, uint32_t instruction) { // TODO conside
             imm |= ((instruction >> 20) & 0x1) << 11;          // imm[11]
             imm |= ((instruction >> 21) & 0x3FF) << 1;         // imm[10:1]
 
-            printf("JAL: Jumped to 0x%X + %d = 0x%X (inst %u), link x%u", cpu->programCounter, imm, cpu->programCounter, cpu->programCounter / 4, rd);
-
             registers[rd] = cpu->programCounter + 4;
             cpu->programCounter += imm - 4; // program counter is incremented after this, and we have to execute the function we point to
 
+            printf("JAL: Jumped to 0x%X + %d = 0x%X (inst %u), link x%u", registers[rd], imm, cpu->programCounter, cpu->programCounter / 4, rd);
+
             break;
         }
         case 0b0110011: { // OP for Integer Register-Register Operations (R type)
@@ -279,25 +279,95 @@ int execute_instruction_on_cpu(CPU *cpu, uint32_t instruction) { // TODO conside
         }
         case 0b1110011: {// SYSTEM (I type) instructions are used to access system functionality that might require privileged access, here only ECALL and EBREAK
             uint8_t funct3 = instruction >> 12 & 0x7;
-            uint32_t rs1 = instruction >> 15 & 0x1F;
-            uint32_t func12 = instruction >> 20 & 0xFFF;
+            uint32_t rs1 = instruction >> 15 & 0x1F; // aka uimm. zero extends automatically
+            uint32_t func12 = instruction >> 20 & 0xFFF; // aka csr
 
-            if (funct3 != 0 || rd != 0 || rs1 != 0) {
-                fprintf(stderr, "For SYSTEM instructions, funct3, rd and rs1 must be 0, but are 0x%X, 0x%X and 0x%X. Zicsr is not supported.", funct3, rd, rs1);
-                return 2;
+            if (funct3 == 0) {
+                if (rd != 0 || rs1 != 0) {
+                    fprintf(stderr, "For ECALL / EBREAK instructions, rd and rs1 must be 0, but are 0x%X and 0x%X.", rd, rs1);
+                    return 2;
+                }
+
+                if (func12 == 0) { // ECALL
+                    printf("--- ECALL ---");
+                    // TODO do something
+                } else if (func12 == 1) { // EBREAK
+                    printf("--- EBREAK---");
+                    // TODO do something
+                } else {
+                    fprintf(stderr, "Invalid func12 for environment instructions: 0x%X", func12);
+                    //return 2;
+                }
+            } else if (funct3 == 0b001) { // CSRRW Atomic Read/Write CSR
+                printf("CSRRW: (0x%X <-> x%u) -> x%u", func12, rs1, rd);
+                if (rd != 0) {
+                    uint32_t csrValue;
+                    read_csr(addressSpace, func12, &csrValue);
+
+                    write_csr(addressSpace, func12, registers + rs1);
+                    registers[rd] = csrValue;
+                } else {
+                    write_csr(addressSpace, func12, registers + rs1);
+                }
+            } else if (funct3 == 0b010) { // CSRRS Atomic Read and Set Bits in CSR
+                printf("CSRRS: (0x%X | x%u) -> x%u", func12, rs1, rd);
+                uint32_t csrValue;
+                read_csr(addressSpace, func12, &csrValue);
+
+                if (rs1 != 0) {
+                    uint32_t csrValueNew = csrValue | registers[rs1];
+                    write_csr(addressSpace, func12, &csrValueNew);
+                }
+                
+                registers[rd] = csrValue;
+            } else if (funct3 == 0b011) { // CSRRC Atomic Read and Clear Bits in CSR
+                printf("CSRRC: (0x%X & ~x%u) -> x%u", func12, rs1, rd);
+                uint32_t csrValue;
+                read_csr(addressSpace, func12, &csrValue);
+
+                if (rs1 != 0) {
+                    uint32_t csrValueNew = csrValue & ~registers[rs1];
+                    write_csr(addressSpace, func12, &csrValueNew);
+                }
+                
+                registers[rd] = csrValue;
+            } else if (funct3 == 0b101) { // CSRRWI Atomic Read/Write CSR Immediate
+                printf("CSRRWI: (0x%X <-> 0x%X) -> x%u", func12, rs1, rd);
+                // since we don't worry about order, we can simplify
+                if (rd != 0) {
+                    read_csr(addressSpace, func12, registers + rd);
+                }
+
+                write_csr(addressSpace, func12, &rs1); // rs1 is uimm
+            } else if (funct3 == 0b110) { // CSRRSI Atomic Read and Set Bits in CSR Immediate
+                printf("CSRRSI: (0x%X | 0x%X) -> x%u", func12, rs1, rd);
+                read_csr(addressSpace, func12, registers + rd);
+
+                if (rs1 != 0) {
+                    uint32_t csrValueNew = registers[rd] | rs1;
+                    write_csr(addressSpace, func12, &csrValueNew);
+                }
+            } else if (funct3 == 0b111) { // CSRRCI Atomic Read and Clear Bits in CSR Immediate
+                printf("CSRRCI: (0x%X & 0x%X) -> x%u", func12, rs1, rd);
+                read_csr(addressSpace, func12, registers + rd);
+
+                if (rs1 != 0) {
+                    uint32_t csrValueNew = registers[rd] & ~rs1;
+                    write_csr(addressSpace, func12, &csrValueNew);
+                }
             }
 
-            if (func12 == 0) { // ECALL
-                printf("--- ECALL ---");
-                // TODO do something
-            } else if (func12 == 1) { // EBREAK
-                printf("--- EBREAK---");
-                // TODO do something
-            } else {
-                fprintf(stderr, "Invalid SYSTEM func12: 0x%X", func12);
-                return 2;
+            break;
+        }
+        case 0b0001111: { // MISC-MEM
+            uint8_t funct3 = instruction >> 12 & 0x7;
+            uint8_t rs1 = instruction >> 15 & 0x1F;
+
+            if (funct3 != 0) {
+                fprintf(stderr, "funct3 in MISC-MEM must be zero, but is 0x%X", funct3);
             }
 
+            // TODO
             break;
         }
         default: {