screen share python
This commit is contained in:
parent
f124ba5585
commit
872f8a0406
Binary file not shown.
98
cpu-emulator/convert.py
Normal file
98
cpu-emulator/convert.py
Normal file
@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import struct
|
||||
|
||||
def run_command(cmd_list, error_msg):
|
||||
try:
|
||||
subprocess.run(cmd_list, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"{error_msg}: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def compile_c_source(source_file, elf_file):
|
||||
# Compile the source file with no standard library.
|
||||
cmd = [
|
||||
"riscv64-unknown-elf-gcc",
|
||||
"-nostdlib",
|
||||
"-static",
|
||||
"-O2",
|
||||
"-o", elf_file,
|
||||
source_file
|
||||
]
|
||||
print("Compiling source file...")
|
||||
run_command(cmd, "Compilation failed")
|
||||
|
||||
def convert_elf_to_bin(elf_file, bin_file):
|
||||
# Convert the compiled ELF to a raw binary.
|
||||
cmd = [
|
||||
"riscv64-unknown-elf-objcopy",
|
||||
"-O", "binary",
|
||||
elf_file,
|
||||
bin_file
|
||||
]
|
||||
print("Converting ELF to raw binary...")
|
||||
run_command(cmd, "Objcopy conversion failed")
|
||||
|
||||
def generate_cpp_vector(bin_file, cpp_file, vector_name="program_data"):
|
||||
|
||||
# Read the binary file.
|
||||
print("Reading raw binary...")
|
||||
with open(bin_file, "rb") as bf:
|
||||
data = bf.read()
|
||||
|
||||
# Group the binary into 32-bit words (assuming little-endian)
|
||||
words = []
|
||||
for i in range(0, len(data), 4):
|
||||
chunk = data[i:i+4]
|
||||
# If the last chunk is not 4 bytes, pad with zeros.
|
||||
if len(chunk) < 4:
|
||||
chunk = chunk.ljust(4, b'\x00')
|
||||
# Unpack little-endian unsigned int.
|
||||
word = struct.unpack("<I", chunk)[0]
|
||||
words.append(word)
|
||||
|
||||
# Create the C++ file with the vector.
|
||||
print("Writing C++ vector file...")
|
||||
with open(cpp_file, "w") as cf:
|
||||
cf.write("// Generated C++ vector containing program data.\n")
|
||||
cf.write("#include <vector>\n")
|
||||
cf.write("#include <cstdint>\n\n")
|
||||
cf.write(f"std::vector<uint32_t> {vector_name} = {{\n")
|
||||
|
||||
# Format: 8 words per line.
|
||||
for i, word in enumerate(words):
|
||||
# Print in hex with 0x... formatting.
|
||||
cf.write(f" 0x{word:08X}, ")
|
||||
if (i + 1) % 8 == 0:
|
||||
cf.write("\n")
|
||||
cf.write("\n};\n")
|
||||
print(f"C++ file '{cpp_file}' generated successfully.")
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: ./build_and_generate.py <source.c> [<output.cpp>]", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
source_file = sys.argv[1]
|
||||
# Default names for intermediate and output files.
|
||||
elf_file = "program.elf"
|
||||
bin_file = "program.bin"
|
||||
cpp_file = sys.argv[2] if len(sys.argv) >= 3 else "program_data.cpp"
|
||||
|
||||
# Check that the source file exists.
|
||||
if not os.path.exists(source_file):
|
||||
print(f"Source file '{source_file}' does not exist.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
compile_c_source(source_file, elf_file)
|
||||
convert_elf_to_bin(elf_file, bin_file)
|
||||
generate_cpp_vector(bin_file, cpp_file)
|
||||
|
||||
# Optionally, clean up the intermediate files.
|
||||
os.remove(elf_file)
|
||||
os.remove(bin_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Binary file not shown.
@ -18,3 +18,28 @@ Pos=764,282
|
||||
Size=1010,331
|
||||
Collapsed=0
|
||||
|
||||
[Window][Debugger]
|
||||
Pos=60,60
|
||||
Size=158,598
|
||||
Collapsed=0
|
||||
|
||||
[Window][Memory Viewer]
|
||||
Pos=60,60
|
||||
Size=1828,340
|
||||
Collapsed=0
|
||||
|
||||
[Window][Disassembly]
|
||||
Pos=395,41
|
||||
Size=205,201
|
||||
Collapsed=0
|
||||
|
||||
[Window][CPU Control]
|
||||
Pos=18,404
|
||||
Size=1384,715
|
||||
Collapsed=0
|
||||
|
||||
[Window][Terminal]
|
||||
Pos=1400,430
|
||||
Size=487,502
|
||||
Collapsed=0
|
||||
|
||||
|
@ -1,282 +1,599 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
// main.cpp
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
// OpenGL/GLFW and GLEW headers.
|
||||
#include <GL/glew.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
// ImGui headers.
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <GLFW/glfw3.h>
|
||||
#include "imgui_memory_editor.h" // Add this header for the Memory Editor
|
||||
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 64
|
||||
#define SCREEN_PIXELS (SCREEN_WIDTH * SCREEN_HEIGHT)
|
||||
#define SCREEN_MEM_START 0x1000
|
||||
#define SCREEN_MEM_SIZE (SCREEN_PIXELS * 3) // RGB888
|
||||
#include "out.h"
|
||||
|
||||
// ========== CPU ==========
|
||||
class CPU {
|
||||
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// CPU Flags structure (for debugging and visualization)
|
||||
// --------------------------------------------------------------------------------
|
||||
struct CPUFlags {
|
||||
bool zero;
|
||||
bool negative;
|
||||
bool carry;
|
||||
bool overflow;
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// RISC-V Emulator Class.
|
||||
// This example implements a subset of the RV32I instructions plus ECALL for
|
||||
// basic system calls that print data to a virtual terminal.
|
||||
class RiscVEmulator {
|
||||
public:
|
||||
uint8_t A = 0;
|
||||
uint8_t CMPF = 0;
|
||||
uint16_t PC = 0;
|
||||
bool running = true;
|
||||
std::vector<uint8_t> memory;
|
||||
// 32 general-purpose registers and the program counter.
|
||||
uint32_t registers[32];
|
||||
uint32_t pc;
|
||||
// Memory implemented as an array of 32-bit words.
|
||||
std::vector<uint32_t> memory;
|
||||
// CPU flags for arithmetic operations.
|
||||
CPUFlags flags;
|
||||
// A flag to halt execution in case of error.
|
||||
bool halted;
|
||||
// A buffer to collect terminal output (our “screen”).
|
||||
std::string terminalOutput;
|
||||
|
||||
CPU() : memory(65536, 0) {}
|
||||
// Constructor: allocate memory (memSize in words) and reset the CPU.
|
||||
RiscVEmulator(size_t memSize)
|
||||
: pc(0), memory(memSize, 0), halted(false)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void loadProgram(const std::vector<uint8_t>& program) {
|
||||
std::fill(memory.begin(), memory.end(), 0);
|
||||
for (size_t i = 0; i < program.size(); i++)
|
||||
// Reset CPU state and (optionally) memory.
|
||||
void reset() {
|
||||
pc = 0;
|
||||
std::memset(registers, 0, sizeof(registers));
|
||||
flags = { false, false, false, false };
|
||||
halted = false;
|
||||
terminalOutput.clear();
|
||||
}
|
||||
|
||||
// Load a program (vector of 32-bit instructions) into memory.
|
||||
void loadProgram(const std::vector<uint32_t>& program) {
|
||||
for (size_t i = 0; i < program.size() && i < memory.size(); i++) {
|
||||
memory[i] = program[i];
|
||||
PC = 0;
|
||||
A = 0;
|
||||
CMPF = 0;
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to update flags on addition.
|
||||
void updateFlagsForAddition(uint32_t op1, uint32_t op2, uint32_t result) {
|
||||
flags.zero = (result == 0);
|
||||
flags.negative = (static_cast<int32_t>(result) < 0);
|
||||
flags.carry = (result < op1);
|
||||
bool sign1 = (static_cast<int32_t>(op1) < 0);
|
||||
bool sign2 = (static_cast<int32_t>(op2) < 0);
|
||||
bool signr = (static_cast<int32_t>(result) < 0);
|
||||
flags.overflow = ((sign1 == sign2) && (signr != sign1));
|
||||
}
|
||||
|
||||
// Helper to update flags on subtraction.
|
||||
void updateFlagsForSubtraction(uint32_t op1, uint32_t op2, uint32_t result) {
|
||||
flags.zero = (result == 0);
|
||||
flags.negative = (static_cast<int32_t>(result) < 0);
|
||||
flags.carry = (op1 < op2);
|
||||
bool sign1 = (static_cast<int32_t>(op1) < 0);
|
||||
bool sign2 = (static_cast<int32_t>(op2) < 0);
|
||||
bool signr = (static_cast<int32_t>(result) < 0);
|
||||
flags.overflow = ((sign1 != sign2) && (signr != sign1));
|
||||
}
|
||||
|
||||
// Helper: Read a null-terminated string from memory starting at the given address.
|
||||
// This function assumes that the string is stored in little-endian order.
|
||||
std::string readString(uint32_t address) {
|
||||
std::string result;
|
||||
while (true) {
|
||||
if (address / 4 >= memory.size())
|
||||
break;
|
||||
uint32_t word = memory[address / 4];
|
||||
// Extract 4 bytes from the word.
|
||||
for (int i = 0; i < 4; i++) {
|
||||
char ch = (char)((word >> (i * 8)) & 0xFF);
|
||||
if (ch == '\0') return result;
|
||||
result.push_back(ch);
|
||||
}
|
||||
address += 4;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Execute one instruction (fetch-decode-execute).
|
||||
void step() {
|
||||
uint8_t opcode = memory[PC++];
|
||||
switch (opcode) {
|
||||
case 0x01: A = memory[PC++]; break; // LDA #val
|
||||
case 0x02: A += memory[PC++]; break; // ADD #val
|
||||
case 0x03: memory[memory[PC++]] = A; break; // STA addr
|
||||
case 0x04: A = memory[memory[PC++]]; break; // LDM addr
|
||||
case 0x05: {
|
||||
uint8_t addr = memory[PC++];
|
||||
PC = addr;
|
||||
break;
|
||||
}
|
||||
case 0x06: {
|
||||
uint8_t addr = memory[PC++];
|
||||
if (A == 0) PC = addr;
|
||||
break;
|
||||
}
|
||||
case 0x07: running = false; break; // HLT
|
||||
case 0x08: A -= memory[PC++]; break; // SUB #val
|
||||
case 0x09: {
|
||||
uint8_t addr = memory[PC++];
|
||||
if (A != 0) PC = addr;
|
||||
break;
|
||||
}
|
||||
case 0x0A: CMPF = A == memory[PC++]; break; // CMP #val
|
||||
case 0x0B: break; // NOP
|
||||
|
||||
// Flow control
|
||||
case 0x0C: { // BEQ
|
||||
uint8_t addr = memory[PC++];
|
||||
if (A == 0) PC = addr;
|
||||
break;
|
||||
}
|
||||
case 0x0D: { // BNE
|
||||
uint8_t addr = memory[PC++];
|
||||
if (A != 0) PC = addr;
|
||||
break;
|
||||
}
|
||||
case 0x0E: { // BMI (A & 0x80 != 0)
|
||||
uint8_t addr = memory[PC++];
|
||||
if (A & 0x80) PC = addr;
|
||||
break;
|
||||
}
|
||||
case 0x0F: { // BPL (A & 0x80 == 0)
|
||||
uint8_t addr = memory[PC++];
|
||||
if (!(A & 0x80)) PC = addr;
|
||||
break;
|
||||
}
|
||||
case 0x10: { // BRA (Unconditional branch)
|
||||
uint8_t addr = memory[PC++];
|
||||
PC = addr;
|
||||
break;
|
||||
}
|
||||
case 0x11: { // CMP #val
|
||||
uint8_t value = memory[PC++];
|
||||
CMPF = A == value;
|
||||
break;
|
||||
}
|
||||
case 0x12: { // BCC (Branch if carry clear)
|
||||
uint8_t addr = memory[PC++];
|
||||
if (!(A & 0x01)) PC = addr; // Using the least significant bit of A for carry flag
|
||||
break;
|
||||
}
|
||||
case 0x13: { // BCS (Branch if carry set)
|
||||
uint8_t addr = memory[PC++];
|
||||
if (A & 0x01) PC = addr;
|
||||
break;
|
||||
}
|
||||
|
||||
default: running = false; break;
|
||||
if (halted) return;
|
||||
if (pc % 4 != 0 || (pc / 4) >= memory.size()) {
|
||||
halted = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void run(int steps = 1) {
|
||||
while (running && steps-- > 0) step();
|
||||
}
|
||||
|
||||
std::string getCurrentInstruction() {
|
||||
uint8_t opcode = memory[PC];
|
||||
char buffer[64];
|
||||
uint32_t inst = memory[pc / 4];
|
||||
uint32_t opcode = inst & 0x7F;
|
||||
|
||||
switch (opcode) {
|
||||
case 0x01: sprintf(buffer, "LDA 0x%02X", memory[PC + 1]); break;
|
||||
case 0x02: sprintf(buffer, "ADD 0x%02X", memory[PC + 1]); break;
|
||||
case 0x03: sprintf(buffer, "STA 0x%02X", memory[PC + 1]); break;
|
||||
case 0x04: sprintf(buffer, "LDM 0x%02X", memory[PC + 1]); break;
|
||||
case 0x05: sprintf(buffer, "JMP 0x%02X", memory[PC + 1]); break;
|
||||
case 0x06: sprintf(buffer, "JEZ 0x%02X", memory[PC + 1]); break;
|
||||
case 0x07: sprintf(buffer, "HLT"); break;
|
||||
case 0x08: sprintf(buffer, "SUB 0x%02X", memory[PC + 1]); break;
|
||||
case 0x09: sprintf(buffer, "JNZ 0x%02X", memory[PC + 1]); break;
|
||||
case 0x0A: sprintf(buffer, "CMP 0x%02X", memory[PC + 1]); break;
|
||||
case 0x0B: sprintf(buffer, "NOP"); break;
|
||||
case 0x0C: sprintf(buffer, "BEQ 0x%02X", memory[PC + 1]); break;
|
||||
case 0x0D: sprintf(buffer, "BNE 0x%02X", memory[PC + 1]); break;
|
||||
case 0x0E: sprintf(buffer, "BMI 0x%02X", memory[PC + 1]); break;
|
||||
case 0x0F: sprintf(buffer, "BPL 0x%02X", memory[PC + 1]); break;
|
||||
case 0x10: sprintf(buffer, "BRA 0x%02X", memory[PC + 1]); break;
|
||||
case 0x11: sprintf(buffer, "CMP 0x%02X", memory[PC + 1]); break;
|
||||
case 0x12: sprintf(buffer, "BCC 0x%02X", memory[PC + 1]); break;
|
||||
case 0x13: sprintf(buffer, "BCS 0x%02X", memory[PC + 1]); break;
|
||||
default: sprintf(buffer, "??? 0x%02X", opcode); break;
|
||||
|
||||
// R-type instructions.
|
||||
case 0x33: {
|
||||
uint32_t rd = (inst >> 7) & 0x1F;
|
||||
uint32_t rs1 = (inst >> 15) & 0x1F;
|
||||
uint32_t rs2 = (inst >> 20) & 0x1F;
|
||||
uint32_t funct3 = (inst >> 12) & 0x7;
|
||||
uint32_t funct7 = (inst >> 25) & 0x7F;
|
||||
switch (funct3) {
|
||||
case 0x0:
|
||||
if (funct7 == 0x00) { // ADD
|
||||
uint32_t res = registers[rs1] + registers[rs2];
|
||||
registers[rd] = res;
|
||||
updateFlagsForAddition(registers[rs1], registers[rs2], res);
|
||||
} else if (funct7 == 0x20) { // SUB
|
||||
uint32_t res = registers[rs1] - registers[rs2];
|
||||
registers[rd] = res;
|
||||
updateFlagsForSubtraction(registers[rs1], registers[rs2], res);
|
||||
}
|
||||
break;
|
||||
case 0x1: // SLL
|
||||
registers[rd] = registers[rs1] << (registers[rs2] & 0x1F);
|
||||
break;
|
||||
case 0x2: // SLT
|
||||
registers[rd] = ((int32_t)registers[rs1] < (int32_t)registers[rs2]) ? 1 : 0;
|
||||
break;
|
||||
case 0x3: // SLTU
|
||||
registers[rd] = (registers[rs1] < registers[rs2]) ? 1 : 0;
|
||||
break;
|
||||
case 0x4: // XOR
|
||||
registers[rd] = registers[rs1] ^ registers[rs2];
|
||||
break;
|
||||
case 0x5:
|
||||
if (funct7 == 0x00) { // SRL
|
||||
registers[rd] = registers[rs1] >> (registers[rs2] & 0x1F);
|
||||
} else if (funct7 == 0x20) { // SRA
|
||||
registers[rd] = ((int32_t)registers[rs1]) >> (registers[rs2] & 0x1F);
|
||||
}
|
||||
break;
|
||||
case 0x6: // OR
|
||||
registers[rd] = registers[rs1] | registers[rs2];
|
||||
break;
|
||||
case 0x7: // AND
|
||||
registers[rd] = registers[rs1] & registers[rs2];
|
||||
break;
|
||||
}
|
||||
pc += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
return std::string(buffer);
|
||||
// I-type arithmetic instructions.
|
||||
case 0x13: {
|
||||
uint32_t rd = (inst >> 7) & 0x1F;
|
||||
uint32_t rs1 = (inst >> 15) & 0x1F;
|
||||
uint32_t funct3 = (inst >> 12) & 0x7;
|
||||
int32_t imm = ((int32_t)inst) >> 20;
|
||||
switch (funct3) {
|
||||
case 0x0: { // ADDI
|
||||
uint32_t res = registers[rs1] + imm;
|
||||
registers[rd] = res;
|
||||
updateFlagsForAddition(registers[rs1], imm, res);
|
||||
break;
|
||||
}
|
||||
case 0x2: // SLTI
|
||||
registers[rd] = (((int32_t)registers[rs1]) < imm) ? 1 : 0;
|
||||
break;
|
||||
case 0x3: // SLTIU
|
||||
registers[rd] = (registers[rs1] < (uint32_t)imm) ? 1 : 0;
|
||||
break;
|
||||
case 0x4: // XORI
|
||||
registers[rd] = registers[rs1] ^ imm;
|
||||
break;
|
||||
case 0x6: // ORI
|
||||
registers[rd] = registers[rs1] | imm;
|
||||
break;
|
||||
case 0x7: // ANDI
|
||||
registers[rd] = registers[rs1] & imm;
|
||||
break;
|
||||
case 0x1: { // SLLI
|
||||
uint32_t shamt = imm & 0x1F;
|
||||
registers[rd] = registers[rs1] << shamt;
|
||||
break;
|
||||
}
|
||||
case 0x5: {
|
||||
uint32_t shamt = imm & 0x1F;
|
||||
uint32_t imm_high = (inst >> 25) & 0x7F;
|
||||
if (imm_high == 0x00) { // SRLI
|
||||
registers[rd] = registers[rs1] >> shamt;
|
||||
} else if (imm_high == 0x20) { // SRAI
|
||||
registers[rd] = ((int32_t)registers[rs1]) >> shamt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
pc += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
// Branch instructions (B-type).
|
||||
case 0x63: {
|
||||
uint32_t rs1 = (inst >> 15) & 0x1F;
|
||||
uint32_t rs2 = (inst >> 20) & 0x1F;
|
||||
uint32_t funct3 = (inst >> 12) & 0x7;
|
||||
int32_t imm = (((inst >> 31) & 0x1) << 12) |
|
||||
(((inst >> 25) & 0x3F) << 5) |
|
||||
(((inst >> 8) & 0xF) << 1) |
|
||||
(((inst >> 7) & 0x1) << 11);
|
||||
if (imm & (1 << 12))
|
||||
imm |= 0xFFFFE000;
|
||||
switch (funct3) {
|
||||
case 0x0: // BEQ
|
||||
pc = (registers[rs1] == registers[rs2]) ? (pc + imm) : (pc + 4);
|
||||
break;
|
||||
case 0x1: // BNE
|
||||
pc = (registers[rs1] != registers[rs2]) ? (pc + imm) : (pc + 4);
|
||||
break;
|
||||
case 0x4: // BLT
|
||||
pc = (((int32_t)registers[rs1]) < ((int32_t)registers[rs2])) ? (pc + imm) : (pc + 4);
|
||||
break;
|
||||
case 0x5: // BGE
|
||||
pc = (((int32_t)registers[rs1]) >= ((int32_t)registers[rs2])) ? (pc + imm) : (pc + 4);
|
||||
break;
|
||||
case 0x6: // BLTU
|
||||
pc = (registers[rs1] < registers[rs2]) ? (pc + imm) : (pc + 4);
|
||||
break;
|
||||
case 0x7: // BGEU
|
||||
pc = (registers[rs1] >= registers[rs2]) ? (pc + imm) : (pc + 4);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// JAL: J-type jump.
|
||||
case 0x6F: {
|
||||
uint32_t rd = (inst >> 7) & 0x1F;
|
||||
int32_t imm = (((inst >> 31) & 0x1) << 20) |
|
||||
(((inst >> 21) & 0x3FF) << 1) |
|
||||
(((inst >> 20) & 0x1) << 11) |
|
||||
(((inst >> 12) & 0xFF) << 12);
|
||||
if (imm & (1 << 20))
|
||||
imm |= 0xFFF00000;
|
||||
registers[rd] = pc + 4;
|
||||
pc += imm;
|
||||
break;
|
||||
}
|
||||
|
||||
// JALR: I-type jump.
|
||||
case 0x67: {
|
||||
uint32_t rd = (inst >> 7) & 0x1F;
|
||||
uint32_t rs1 = (inst >> 15) & 0x1F;
|
||||
int32_t imm = ((int32_t)inst) >> 20;
|
||||
uint32_t temp = pc + 4;
|
||||
pc = (registers[rs1] + imm) & ~1;
|
||||
registers[rd] = temp;
|
||||
break;
|
||||
}
|
||||
|
||||
// LUI: U-type.
|
||||
case 0x37: {
|
||||
uint32_t rd = (inst >> 7) & 0x1F;
|
||||
registers[rd] = inst & 0xFFFFF000;
|
||||
pc += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
// AUIPC: U-type.
|
||||
case 0x17: {
|
||||
uint32_t rd = (inst >> 7) & 0x1F;
|
||||
registers[rd] = pc + (inst & 0xFFFFF000);
|
||||
pc += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
// Load instructions (LW only).
|
||||
case 0x03: {
|
||||
uint32_t rd = (inst >> 7) & 0x1F;
|
||||
uint32_t rs1 = (inst >> 15) & 0x1F;
|
||||
uint32_t funct3 = (inst >> 12) & 0x7;
|
||||
int32_t imm = ((int32_t)inst) >> 20;
|
||||
if (funct3 == 0x2) { // LW
|
||||
uint32_t addr = registers[rs1] + imm;
|
||||
if (addr % 4 == 0 && (addr / 4) < memory.size())
|
||||
registers[rd] = memory[addr / 4];
|
||||
}
|
||||
pc += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
// Store instructions (SW only).
|
||||
case 0x23: {
|
||||
uint32_t rs1 = (inst >> 15) & 0x1F;
|
||||
uint32_t rs2 = (inst >> 20) & 0x1F;
|
||||
uint32_t funct3 = (inst >> 12) & 0x7;
|
||||
int32_t imm = (((inst >> 25) & 0x7F) << 5) | ((inst >> 7) & 0x1F);
|
||||
if (imm & 0x800) imm |= 0xFFFFF000;
|
||||
if (funct3 == 0x2) { // SW
|
||||
uint32_t addr = registers[rs1] + imm;
|
||||
if (addr % 4 == 0 && (addr / 4) < memory.size())
|
||||
memory[addr / 4] = registers[rs2];
|
||||
}
|
||||
pc += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
// ECALL: Opcode 0x73 (environment call).
|
||||
// This branch simulates interrupts/system calls to “print” data to our terminal.
|
||||
case 0x73: {
|
||||
// For ECALL, we follow a simple convention:
|
||||
// - Register x17 (a7) holds the system call number.
|
||||
// - Register x10 (a0) holds the argument.
|
||||
// Conventions used below:
|
||||
// 1: Print integer (from x10).
|
||||
// 2: Print character (the low-order 8 bits of x10).
|
||||
// 3: Print string (address pointer stored in x10).
|
||||
uint32_t funct3 = (inst >> 12) & 0x7;
|
||||
if (funct3 == 0) {
|
||||
uint32_t syscall = registers[17]; // a7
|
||||
switch (syscall) {
|
||||
case 1: { // Print integer.
|
||||
int value = (int) registers[10]; // a0
|
||||
terminalOutput += std::to_string(value) + "\n";
|
||||
break;
|
||||
}
|
||||
case 2: { // Print character.
|
||||
char ch = (char)(registers[10] & 0xFF);
|
||||
terminalOutput.push_back(ch);
|
||||
break;
|
||||
}
|
||||
case 3: { // Print string (pointer in a0).
|
||||
std::string str = readString(registers[10]);
|
||||
terminalOutput += str;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
terminalOutput += "Unknown ECALL: " + std::to_string(syscall) + "\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
pc += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Unknown opcode – halt the CPU.
|
||||
halted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Enforce that register x0 is always 0.
|
||||
registers[0] = 0;
|
||||
}
|
||||
|
||||
// Disassemble an instruction at the given address.
|
||||
std::string disassembleInstruction(uint32_t address) {
|
||||
if (address % 4 != 0 || (address / 4) >= memory.size())
|
||||
return "Invalid address";
|
||||
uint32_t inst = memory[address / 4];
|
||||
uint32_t opcode = inst & 0x7F;
|
||||
std::stringstream ss;
|
||||
ss << "0x" << std::hex << inst << std::dec << " ";
|
||||
switch (opcode) {
|
||||
case 0x33: { // R-type
|
||||
uint32_t rd = (inst >> 7) & 0x1F;
|
||||
uint32_t rs1 = (inst >> 15) & 0x1F;
|
||||
uint32_t rs2 = (inst >> 20) & 0x1F;
|
||||
uint32_t funct3 = (inst >> 12) & 0x7;
|
||||
uint32_t funct7 = (inst >> 25) & 0x7F;
|
||||
if (funct3 == 0x0 && funct7 == 0x00)
|
||||
ss << "ADD x" << rd << ", x" << rs1 << ", x" << rs2;
|
||||
else if (funct3 == 0x0 && funct7 == 0x20)
|
||||
ss << "SUB x" << rd << ", x" << rs1 << ", x" << rs2;
|
||||
else
|
||||
ss << "R-type (0x" << std::hex << inst << ")";
|
||||
break;
|
||||
}
|
||||
case 0x13:
|
||||
ss << "ADDI (or other I-type)";
|
||||
break;
|
||||
case 0x63:
|
||||
ss << "Branch/Compare";
|
||||
break;
|
||||
case 0x6F:
|
||||
ss << "JAL";
|
||||
break;
|
||||
case 0x67:
|
||||
ss << "JALR";
|
||||
break;
|
||||
case 0x37:
|
||||
ss << "LUI";
|
||||
break;
|
||||
case 0x17:
|
||||
ss << "AUIPC";
|
||||
break;
|
||||
case 0x03:
|
||||
ss << "LW";
|
||||
break;
|
||||
case 0x23:
|
||||
ss << "SW";
|
||||
break;
|
||||
case 0x73:
|
||||
ss << "ECALL";
|
||||
break;
|
||||
default:
|
||||
ss << "Unknown opcode";
|
||||
break;
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
// ========== OpenGL Pixel Renderer ==========
|
||||
void renderScreenToBackground(uint8_t* memory) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImDrawList* bg = ImGui::GetBackgroundDrawList();
|
||||
float pixelSize = io.DisplaySize.x / SCREEN_WIDTH;
|
||||
|
||||
for (int y = 0; y < SCREEN_HEIGHT; ++y) {
|
||||
for (int x = 0; x < SCREEN_WIDTH; ++x) {
|
||||
int index = SCREEN_MEM_START + (y * SCREEN_WIDTH + x) * 3;
|
||||
uint8_t r = memory[index + 0];
|
||||
uint8_t g = memory[index + 1];
|
||||
uint8_t b = memory[index + 2];
|
||||
ImVec2 tl(x * pixelSize, y * pixelSize);
|
||||
ImVec2 br(tl.x + pixelSize, tl.y + pixelSize);
|
||||
bg->AddRectFilled(tl, br, IM_COL32(r, g, b, 255));
|
||||
}
|
||||
// --------------------------------------------------------------------------------
|
||||
// Main: OpenGL/GLFW + ImGui Debugger and Terminal UI.
|
||||
// --------------------------------------------------------------------------------
|
||||
int main(int, char**) {
|
||||
// Initialize GLFW.
|
||||
if (!glfwInit()) {
|
||||
fprintf(stderr, "Failed to initialize GLFW\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Main ==========
|
||||
int main() {
|
||||
if (!glfwInit()) return -1;
|
||||
GLFWwindow* window = glfwCreateWindow(1024, 640, "Rainbow CPU Emulator", NULL, NULL);
|
||||
const char* glsl_version = "#version 130";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
GLFWwindow* window = glfwCreateWindow(1280, 720, "RISC-V Emulator with Terminal", NULL, NULL);
|
||||
if (window == nullptr)
|
||||
return 1;
|
||||
glfwMakeContextCurrent(window);
|
||||
glfwSwapInterval(1);
|
||||
|
||||
// Initialize ImGui.
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init("#version 130");
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
ImGui::StyleColorsDark();
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
|
||||
CPU cpu;
|
||||
std::vector<uint8_t> rainbow = {
|
||||
0x01, 0xFF, // LDA #0xFF
|
||||
0x03, 0x00, // STA $00 (R)
|
||||
0x01, 0x00,
|
||||
0x03, 0x01, // G
|
||||
0x01, 0x00,
|
||||
0x03, 0x02, // B
|
||||
0x01, 0x00,
|
||||
0x03, 0x10, // pixelIndex = 0
|
||||
// Create the emulator instance with 1024 words of memory.
|
||||
RiscVEmulator emulator(1024);
|
||||
|
||||
// loop:
|
||||
0x04, 0x00, 0x03, 0x20, // load R -> $20
|
||||
0x04, 0x01, 0x03, 0x21, // G -> $21
|
||||
0x04, 0x02, 0x03, 0x22, // B -> $22
|
||||
|
||||
// Write to screen: mem[0x1000 + pixelIndex * 3]
|
||||
0x04, 0x10, // LDM pixelIndex
|
||||
0x02, 0x00, // ADD #0 (A = index)
|
||||
0x03, 0x11, // Store to $11 (low index)
|
||||
emulator.loadProgram(program_data);
|
||||
|
||||
// simulate writing to screen: here you can inject into actual screen mem if you simulate 16-bit
|
||||
// Debugger control flags.
|
||||
bool running = false;
|
||||
double lastStepTime = glfwGetTime();
|
||||
const double stepInterval = 0.1; // Auto-step every 0.1 sec in "running" mode.
|
||||
|
||||
0x04, 0x00, 0x03, 0x00, // Rotate R -> temp
|
||||
0x04, 0x01, 0x03, 0x00,
|
||||
0x04, 0x02, 0x03, 0x01,
|
||||
0x04, 0x00, 0x03, 0x02,
|
||||
|
||||
0x02, 0x01, // ADD #1 (pixelIndex++)
|
||||
0x03, 0x10, // STA pixelIndex
|
||||
|
||||
0x05, 0x10 // JMP to loop start
|
||||
};
|
||||
|
||||
cpu.loadProgram(rainbow);
|
||||
|
||||
static MemoryEditor mem_edit_1;
|
||||
// static char data[0x10000];
|
||||
// size_t data_size = 0x10000;
|
||||
|
||||
// --- Main loop ---
|
||||
// Main loop.
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
glfwPollEvents();
|
||||
double currentTime = glfwGetTime();
|
||||
if (running && (currentTime - lastStepTime) >= stepInterval && !emulator.halted) {
|
||||
emulator.step();
|
||||
lastStepTime = currentTime;
|
||||
}
|
||||
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
renderScreenToBackground(cpu.memory.data());
|
||||
|
||||
ImGui::Begin("CPU Emulator");
|
||||
|
||||
if (ImGui::Button("Step")) cpu.step();
|
||||
// CPU Control Panel.
|
||||
ImGui::Begin("CPU Control");
|
||||
if (ImGui::Button("Reset")) {
|
||||
emulator.reset();
|
||||
emulator.loadProgram(program);
|
||||
running = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Run")) cpu.run(10000);
|
||||
if (ImGui::Button("Start"))
|
||||
running = true;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reset")) cpu.loadProgram(rainbow);
|
||||
|
||||
ImGui::Text("A: 0x%02X | PC: 0x%04X", cpu.A, cpu.PC);
|
||||
ImGui::Text("Instruction: %s", cpu.getCurrentInstruction().c_str());
|
||||
|
||||
ImGui::End();
|
||||
|
||||
|
||||
mem_edit_1.DrawWindow("Memory Editor", &cpu.memory, 65536);
|
||||
|
||||
|
||||
ImGui::Begin("Memory Inspector");
|
||||
|
||||
static int selected = 0;
|
||||
ImGui::SliderInt("Address", &selected, 0, (int)cpu.memory.size() - 1);
|
||||
ImGui::Text("0x%04X: 0x%02X", selected, cpu.memory[selected]);
|
||||
if (ImGui::Button("Zero")) cpu.memory[selected] = 0;
|
||||
|
||||
if (ImGui::Button("Stop"))
|
||||
running = false;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Step") && !emulator.halted)
|
||||
emulator.step();
|
||||
ImGui::Text("PC: 0x%08X", emulator.pc);
|
||||
if (emulator.halted)
|
||||
ImGui::TextColored(ImVec4(1, 0, 0, 1), "CPU Halted");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Full Memory Dump:");
|
||||
|
||||
ImGui::BeginChild("memory_dump", ImVec2(500, 300), true);
|
||||
for (int row = 0; row < 16; ++row) {
|
||||
for (int col = 0; col < 16; ++col) {
|
||||
int addr = row * 16 + col;
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("0x%02X", cpu.memory[addr]);
|
||||
// Registers display.
|
||||
if (ImGui::BeginTable("Registers", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
|
||||
ImGui::TableSetupColumn("Reg");
|
||||
ImGui::TableSetupColumn("Value");
|
||||
ImGui::TableSetupColumn("Reg");
|
||||
ImGui::TableSetupColumn("Value");
|
||||
ImGui::TableHeadersRow();
|
||||
for (int i = 0; i < 32; i += 2) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("x%d", i);
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::Text("0x%08X", emulator.registers[i]);
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImGui::Text("x%d", i+1);
|
||||
ImGui::TableSetColumnIndex(3);
|
||||
ImGui::Text("0x%08X", emulator.registers[i+1]);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::Text("Flags: Z[%d] N[%d] C[%d] O[%d]",
|
||||
emulator.flags.zero, emulator.flags.negative,
|
||||
emulator.flags.carry, emulator.flags.overflow);
|
||||
ImGui::Separator();
|
||||
|
||||
// Disassembly view.
|
||||
ImGui::BeginChild("Disassembly", ImVec2(0, 150), true);
|
||||
ImGui::Text("Disassembly:");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
uint32_t addr = emulator.pc + i * 4;
|
||||
std::string line = emulator.disassembleInstruction(addr);
|
||||
if (i == 0)
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "0x%08X: %s", addr, line.c_str());
|
||||
else
|
||||
ImGui::Text("0x%08X: %s", addr, line.c_str());
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::End();
|
||||
|
||||
// Memory Viewer in a grid.
|
||||
ImGui::Begin("Memory Viewer");
|
||||
const int columns = 16, rows = 16;
|
||||
if (ImGui::BeginTable("MemoryGrid", columns, ImGuiTableFlags_Borders)) {
|
||||
for (int col = 0; col < columns; col++) {
|
||||
ImGui::TableSetupColumn("");
|
||||
}
|
||||
ImGui::TableHeadersRow();
|
||||
int wordIndex = 0;
|
||||
for (int r = 0; r < rows; r++) {
|
||||
ImGui::TableNextRow();
|
||||
for (int c = 0; c < columns; c++) {
|
||||
ImGui::TableSetColumnIndex(c);
|
||||
if (wordIndex < emulator.memory.size())
|
||||
ImGui::Text("0x%08X", emulator.memory[wordIndex]);
|
||||
else
|
||||
ImGui::Text("----");
|
||||
wordIndex++;
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
// Terminal screen to display ECALL outputs.
|
||||
ImGui::Begin("Terminal");
|
||||
// Optionally, you can add a "Clear" button.
|
||||
if (ImGui::Button("Clear"))
|
||||
emulator.terminalOutput.clear();
|
||||
ImGui::Separator();
|
||||
// Display the terminal output in a scrolling region.
|
||||
ImGui::BeginChild("ScrollingRegion", ImVec2(0, 150), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
ImGui::TextUnformatted(emulator.terminalOutput.c_str());
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
|
||||
// Rendering.
|
||||
ImGui::Render();
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(window, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
|
||||
// Cleanup.
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
7
cpu-emulator/out.h
Normal file
7
cpu-emulator/out.h
Normal file
@ -0,0 +1,7 @@
|
||||
// Generated C++ vector containing program data.
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
std::vector<uint32_t> program_data = {
|
||||
0x45051141, 0x4639858A, 0x04000893, 0x00000073, 0x08934501, 0x007305D0, 0xA0010000,
|
||||
};
|
39
cpu-emulator/program.c
Normal file
39
cpu-emulator/program.c
Normal file
@ -0,0 +1,39 @@
|
||||
// hello.c
|
||||
// Minimal RISC-V Linux program without the C library.
|
||||
// It uses direct system calls via the ECALL instruction.
|
||||
//
|
||||
// System call numbers for RISC-V Linux:
|
||||
// __NR_write = 64
|
||||
// __NR_exit = 93
|
||||
//
|
||||
// Compile this program with:
|
||||
// riscv64-unknown-elf-gcc -nostdlib -static -O2 -o hello hello.c
|
||||
|
||||
// _start is the entry point when not using the standard C runtime.
|
||||
void _start() {
|
||||
// Define the message to print.
|
||||
const char msg[] = "Hello, world!\n";
|
||||
|
||||
// --- Write "Hello, world!\n" to stdout ---
|
||||
// For the write(2) system call:
|
||||
// a0: file descriptor (1 for stdout)
|
||||
// a1: pointer to the message
|
||||
// a2: length of the message (without the null terminator)
|
||||
// a7: system call number (__NR_write, which is 64)
|
||||
register long a0 asm("a0") = 1; // file descriptor (stdout)
|
||||
register const char *a1 asm("a1") = msg; // pointer to message
|
||||
register long a2 asm("a2") = sizeof(msg) - 1; // message length
|
||||
register long a7 asm("a7") = 64; // syscall number: write
|
||||
asm volatile ("ecall" : : "r"(a0), "r"(a1), "r"(a2), "r"(a7));
|
||||
|
||||
// --- Exit the program ---
|
||||
// For the exit(2) system call:
|
||||
// a0: exit code (0 for success)
|
||||
// a7: system call number (__NR_exit, which is 93)
|
||||
register long exit_code asm("a0") = 0;
|
||||
register long a7_exit asm("a7") = 93; // syscall number: exit
|
||||
asm volatile ("ecall" : : "r"(exit_code), "r"(a7_exit));
|
||||
|
||||
// We should never reach this point.
|
||||
while (1) {}
|
||||
}
|
28
screen-share/client.py
Normal file
28
screen-share/client.py
Normal file
@ -0,0 +1,28 @@
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
from PIL import ImageGrab
|
||||
import io
|
||||
|
||||
def send_screen():
|
||||
while True:
|
||||
img = ImageGrab.grab()
|
||||
img = img.resize((800, 600))
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format='JPEG', quality=50)
|
||||
data = buf.getvalue()
|
||||
try:
|
||||
s.send(len(data).to_bytes(4, byteorder='big') + data)
|
||||
except:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect(("127.0.0.1", 9999)) # Replace with actual IP
|
||||
threading.Thread(target=send_screen, daemon=True).start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
s.close()
|
78
screen-share/server.py
Normal file
78
screen-share/server.py
Normal file
@ -0,0 +1,78 @@
|
||||
import socket
|
||||
import threading
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from PIL import Image, ImageTk
|
||||
import io
|
||||
|
||||
clients = {}
|
||||
client_frames = {}
|
||||
|
||||
def handle_client(conn, addr, client_id):
|
||||
print(f"[+] New client: {addr} as {client_id}")
|
||||
while True:
|
||||
try:
|
||||
length_data = conn.recv(4)
|
||||
if not length_data:
|
||||
break
|
||||
img_len = int.from_bytes(length_data, byteorder='big')
|
||||
img_data = b''
|
||||
while len(img_data) < img_len:
|
||||
packet = conn.recv(img_len - len(img_data))
|
||||
if not packet:
|
||||
break
|
||||
img_data += packet
|
||||
if client_id in client_frames:
|
||||
image = Image.open(io.BytesIO(img_data))
|
||||
tk_image = ImageTk.PhotoImage(image)
|
||||
canvas = client_frames[client_id]["canvas"]
|
||||
canvas.img = tk_image
|
||||
canvas.create_image(0, 0, anchor="nw", image=tk_image)
|
||||
except:
|
||||
break
|
||||
print(f"[-] Disconnected: {addr}")
|
||||
del clients[client_id]
|
||||
del client_frames[client_id]
|
||||
conn.close()
|
||||
refresh_client_list()
|
||||
|
||||
def refresh_client_list():
|
||||
for widget in client_list_frame.winfo_children():
|
||||
widget.destroy()
|
||||
for client_id in clients:
|
||||
btn = ttk.Button(client_list_frame, text=client_id, command=lambda c=client_id: open_viewer(c))
|
||||
btn.pack(fill="x", padx=5, pady=2)
|
||||
|
||||
def open_viewer(client_id):
|
||||
if client_id in client_frames:
|
||||
return
|
||||
top = tk.Toplevel(root)
|
||||
top.title(f"Screen: {client_id}")
|
||||
canvas = tk.Canvas(top, width=800, height=600)
|
||||
canvas.pack()
|
||||
client_frames[client_id] = {"window": top, "canvas": canvas}
|
||||
|
||||
def accept_clients():
|
||||
while True:
|
||||
conn, addr = server_socket.accept()
|
||||
client_id = f"{addr[0]}:{addr[1]}"
|
||||
clients[client_id] = conn
|
||||
threading.Thread(target=handle_client, args=(conn, addr, client_id), daemon=True).start()
|
||||
refresh_client_list()
|
||||
|
||||
# GUI setup
|
||||
root = tk.Tk()
|
||||
root.title("Screen Viewer Server")
|
||||
root.geometry("300x500")
|
||||
|
||||
client_list_frame = tk.Frame(root)
|
||||
client_list_frame.pack(fill="both", expand=True)
|
||||
|
||||
# Networking
|
||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_socket.bind(("0.0.0.0", 9999))
|
||||
server_socket.listen(5)
|
||||
print("[*] Server listening on port 9999")
|
||||
|
||||
threading.Thread(target=accept_clients, daemon=True).start()
|
||||
root.mainloop()
|
Loading…
Reference in New Issue
Block a user