Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sandbox

Игра "Half a worm"

Ради применения LEG процессора - разработаем игру.

Червячок имеет внушительный размер - аж 4. Расти червячок не может, но и есть не просит, при этом имеет огромные просторы для странствий. 256 байт памяти программы с 4-байтными инструкциями не хватило для полноценной реализации логики игры. Интерес не в самой игре, а в ограничениях при ее создании. Теперь можно закрыть тему 8-битного процессора и ознакомливаться с более мощными архитектурами.

Компонент IO > DIS > Console:

  • Экран 80x24 (80 width, 24 height) 1920 пикселей ASCII символов.
  • Подключается к RAM через Link Components.

8-ми битный процессор может адресовать ячейки только 0-255, но Console 80x24 имеет 1920 ячеек.

Схема "bank + адрес в bank" позволяет имитировать полное адресное пространство.

  • linear - адрес на экране
  • bank - номер окна RAM
  • addr - 8‑битный адрес в окне
  • vram[bank * 256 + addr] - физический адрес в 8‑битной RAM
fn main() {
    const WIDTH: usize = 80;
    const HEIGHT: usize = 24;
    const BANK_SIZE: usize = 256;
    const BANK_COUNT: usize = 8;

    let mut vram = [b'_'; BANK_SIZE * BANK_COUNT];

    let x = 10;
    let y = 5;

    let linear = y * WIDTH + x;
    let bank = linear >> 8;
    let addr = linear & 0xFF;

    vram[bank * BANK_SIZE + addr] = b'A';

    println!(
        "coord=({},{}), linear={}, bank={}, addr={}, value={}",
        x, y, linear, bank, addr, vram[bank * BANK_SIZE + addr] as char
    );
}

Компонет IO > SAN > Keyboard:

  • Для запоминания ввода, добавим регистр
  • Подключается к логике Input
    • 69 left
    • 70 up
    • 72 down
    • 71 right
Прототип:

#![allow(dead_code)]

#[derive(Clone, Debug, Copy)]
enum D{
    R,/*RIGHT*/
    L,/*LEFT*/
    U,/*UP*/
    D/*DOWN*/
}
 
fn next_coords(
    x: usize,
    y: usize,
    mut direct: [D; 4],
    next_step_head: D
) -> Vec<(usize, usize)> {
 
    let mut result = Vec::new();
 
    /*direct[0] = direct[1];
    direct[1] = direct[2];
    direct[2] = direct[3];
    direct[3] = next_step_head;
    */
    //--------------------------
    let r4 = direct[3];     // old ram[3]
    let r5 = direct[2];     // old ram[2]
    direct[2] = r4;         // ram[2] = old ram[3]
    let r4 = direct[1];     // old ram[1]
    direct[1] = r5;         // ram[1] = old ram[2]
    direct[0] = r4;         // ram[0] = old ram[1]
    direct[3] = next_step_head; 
    
    // восстанавливаем тело по направлениям от старой головы к хвосту
    let (mut cur_x, mut cur_y) = (x, y);
    for i in (0..=2).rev() {  
        match direct[i] {
            D::R => cur_x -= 1,
            D::L => cur_x += 1,
            D::U => cur_y += 1,
            D::D => cur_y -= 1,
        }
        
        result.insert(0, (cur_x, cur_y));
    }
    
    
    // новая позиция головы
    let ( cur_x, cur_y) = match next_step_head {
        D::R => (x + 1, y),
        D::L => (x - 1, y),
        D::U => (x, y - 1),
        D::D => (x, y + 1),
    };
    result.push((x, y)); 
    result.push((cur_x, cur_y)); // голова в конец

    result
}

fn main() {
     
    let mut grid = NotebookGrid::new(6, 6);
  
    let x = 2;
    let y = 4;
    let head = {
            (x, y)
    };
    let body_1 = (1, 4);
    let body_2 = (1, 3);
    let body_3 = (1, 2);
    let tail = (0, 2);
     
    // Массив координат змейки
    let snake = vec![
        tail, body_3, body_2, body_1, head
    ];
    
    grid.draw_snake(&snake);
    grid.display();
    
    //==========================================
    // prev step
    //-------------------body_1 body_2 body_3 head
    let direct:[D; 4] = [D::R,  D::D,  D::D,  D::R];  
       
    let next_step_head = D::R;
    
    let snake = next_coords(x,y, direct  ,next_step_head);
  
    grid.draw_snake(&snake);
    grid.display();
}





struct NotebookGrid {
    width: usize,
    height: usize,
    cells: Vec<Vec<bool>>,
}

impl NotebookGrid {
    fn new(width: usize, height: usize) -> Self {
        NotebookGrid {
            width,
            height,
            cells: vec![vec![false; width]; height],
        }
    }
    
    fn fill_cell(&mut self, x: usize, y: usize) {
        if x < self.width && y < self.height {
            self.cells[y][x] = true;
        }
    }
    
    fn clear_cell(&mut self, x: usize, y: usize) {
        if x < self.width && y < self.height {
            self.cells[y][x] = false;
        }
    }
    
    fn fill_rect(&mut self, x1: usize, y1: usize, x2: usize, y2: usize) {
        for y in y1..=y2 {
            for x in x1..=x2 {
                self.fill_cell(x, y);
            }
        }
    }
    
    // Новый метод для отрисовки змейки
    fn draw_snake(&mut self, snake: &[(usize, usize)]) {
        // Очищаем поле
        for y in 0..self.height {
            for x in 0..self.width {
                self.cells[y][x] = false;
            }
        }
        
        // Рисуем змейку
        for &(x, y) in snake {
            self.fill_cell(x, y);
        }
    }
    
    fn display(&self) {
        // Верхний колонтитул с цифрами
        print!(r"y\x");
        for x in 0..self.width {
            if x < 10 {
                print!(" {} ", x);
            } else {
                print!("{} ", x);
            }
        }
        println!();
        
        // Верхняя граница
        print!("   ┌");
        for x in 0..self.width {
            print!("──");
            if x < self.width - 1 {
                print!("┬");
            }
        }
        println!("┐");
        
        // Основное поле
        for y in 0..self.height {
            // Номер строки
            if y < 10 {
                print!(" {} │", y);
            } else {
                print!("{} │", y);
            }
            
            // Клетки
            for x in 0..self.width {
                if self.cells[y][x] {
                    print!("█");
                    print!("█");
                } else {
                    print!("·");
                    print!("·");
                }
                
                if x < self.width - 1 {
                    print!("│");
                }
            }
            println!("│");
            
            // Горизонтальные линии между строками
            if y < self.height - 1 {
                print!("   ├");
                for x in 0..self.width {
                    print!("──");
                    if x < self.width - 1 {
                        print!("┼");
                    }
                }
                println!("┤");
            }
        }
        
        // Нижняя граница
        print!("   └");
        for x in 0..self.width {
            print!("──");
            if x < self.width - 1 {
                print!("┴");
            }
        }
        println!("┘");
    }
}

Для проверок переполнения оси X не хватило места в программе (248 байт), так же и для логики snake (увеличения тела, еды...)


use legassembly::assembly;
fn main(){
  let output_debug = true;
  static INPUT: &str = "
# io output Draw ASCII 48 snake или 0-пусто
# r3 в роли приемника INPUT
# r2 Head X
# r1 Head Y

# Все тело snake храним в RAM [0:tail,1:body,2:body,3:head]
# Fixed snake length - 4 
# Храним только историю направление движения одного шага. 
# RIGHT - 0
# LEFT  - 1
# UP    - 2
# DOWN  - 3
# start position X:3 Y:0
MOV 3, r2 ## Head X

read_input:
    MOV io, r3
   
    # сдвигаем направления body
    MOV 3,r0
    MOV ram,r4      

    MOV 2,r0
    MOV ram,r5      
    MOV r4,ram       

    MOV 1,r0
    MOV ram,r4
    MOV r5,ram       

    MOV 0,r0
    MOV r4,ram  
  
    # сохраним текущее положение
    MOV r1, r4 # copy r1 to r4
    MOV r2, r5 # copy r2 to r5

# высчитываем XY для удаления tail ------------
    # while восстанавливаем тело пропуская head
    MOV 2, r0 ## index array body
    while_find_tail:
        CJE ram, 0, set_body_right
        CJE ram, 1, set_body_left
        CJE ram, 2, set_body_up
        CJE ram, 3, set_body_down ## default

        set_body_down:
            CJE r1, 0, pre_reset_y_up
            SUB r1, 1, r1 ## Head Y
            JMP draw_body
         
        set_body_right:
            SUB r2, 1, r2 ## Head X
            JMP draw_body

        set_body_left:
            ADD 1, r2, r2 ## Head X
            JMP draw_body

        set_body_up:
            CJG r1, 23, pre_reset_y_down
            ADD 1, r1, r1 ## Head Y
            JMP draw_body

        pre_reset_y_up:
            MOV 23, r1   
            JMP draw_body

        pre_reset_y_down:
            MOV 0, r1   
            # JMP draw_body

        draw_body:
            CJE r0, 0, remove_tail
            SUB r0, 1, r0
            JMP while_find_tail

    remove_tail:
        MOV 0 io  ## remove tail
# --------------------------------------
# restor r1,r2
MOV r4, r1
MOV r5, r2

MOV 3, r0 ## для следующего результата по if

CJE r3, 69, set_head_left
CJE r3, 70, set_head_up
CJE r3, 72, set_head_down
# CJE r3, 71, set_head_right
JMP set_head_right ## default set_head_right

set_head_down:
    MOV 3, ram ## next_step_head
    CJG r1, 23, reset_y_up
    ADD 1, r1, r1 ## Head Y
    JMP body_pos
set_head_up:
    MOV 2, ram ## next_step_head
    CJE r1, 0, reset_y_down
    SUB r1, 1, r1 ## Head Y
    JMP body_pos
set_head_left:
    MOV 1, ram ## next_step_head
    # тут должна быть проверка переполнения
    SUB r2, 1, r2 ## Head X
    JMP body_pos
set_head_right:
    MOV 0, ram ## next_step_head
    # тут должна быть проверка переполнения
    ADD 1, r2, r2 ## Head X
    JMP body_pos
reset_y_up:
    MOV 0, r1   
    JMP body_pos
reset_y_down:
    MOV 24, r1   
    # JMP body_pos
body_pos:
    MOV 48 io  ## [X:r2; Y:r1]=48 to output
    JMP read_input
";
 
  assembly(INPUT, output_debug);
}

#[allow(unused_assignments)]
#[allow(unused_variables)]
mod legassembly{

   use self::disassembly::decode;
   use std::collections::HashMap;
    
  #[derive(Debug)]
   enum ParsedLine {
       Label(String),
       Instruction(Instruction),
       Directive(Directive),
       Empty,
   }

   #[derive(Debug)]
   enum Directive {
       Data,
       Text,
       Org(u8),
       Byte(u8),
   }

   #[derive(Debug)]
   enum Operand {
       Source(u8),
       Immediate(u8),
       IndirectAddr(String),
       MemLabel(String),
   }

   #[derive(Debug)]
   struct SourceOperand{
       imm_flag: u8,
       src: Operand
   }
   impl SourceOperand {
       pub fn new(imm_flag: u8, src: Operand) -> Self{
           Self{imm_flag, src}
       }
   }

   #[derive(Debug)]
   enum Instruction {
       Mov { src: SourceOperand, dst: u8 },
       Push { src: SourceOperand},
       Pop { dst: u8 },
       Jmp { label: String },
       Cj { a: SourceOperand, b: SourceOperand, label: String, inst: u8 },
       Add { a: SourceOperand, b: SourceOperand, dst: u8 },
       Sub { a: SourceOperand, b: SourceOperand, dst: u8 },
       And { a: SourceOperand, b: SourceOperand, dst: u8 },
       Or { a: SourceOperand, b: SourceOperand, dst: u8 },
       Not { a: SourceOperand, dst: u8 },
       Xor { a: SourceOperand, b: SourceOperand, dst: u8 },
       Nand { a: SourceOperand, b: SourceOperand, dst: u8 },
       Nor { a: SourceOperand, b: SourceOperand, dst: u8 },
       Div { a: SourceOperand, b: SourceOperand, dst: u8 },
       Call { label: String },
       Ret,
   }

   #[derive(Debug)]
   struct EncodedInstruction {
       opcode: u8,
       arg1: u8,
       arg2: u8,
       result: u8,
   }

   enum AluOp {
       Add,
       Sub,
       And,
       Or,
       Xor,
       Nand,
       Nor,
       Div,
   }

   #[derive(Clone, Copy, PartialEq)]
   enum Section {
       Text,
       Data,
   }

   /*
   jump_start:
       # comment
       MOV 1, r3 # comment
       MOV 2, ram
       MOV 3, pc
       MOV 4, io

       MOV r1, r2
       MOV ram, r2
       MOV ram, ram
       MOV ram, pc
       MOV pc, ram
       MOV io, ram
       MOV io, pc
       MOV io, io

       PUSH r0
       PUSH 255
       PUSH ram
       PUSH io
       PUSH pc
       POP r0
       POP ram
       POP io
       POP pc

       ADD 5, r2, r3
       ADD r1, r2, r3

       SUB 5, r2, r3
       SUB r1, r2, r3

       AND 5, r2, r3
       AND r1, r2, r3

       OR 5, r2, r3
       OR r1, r2, r3

       NOT 5, r3
       NOT r1, r3

       XOR 5, r2, r3
       XOR r1, r2, r3

       NAND 5, r2, r3
       NAND r1, r2, r3 

       NOR 5, r2, r3
       NOR r1, r2, r3 

       DIV 5, r2, r3
       DIV r1, r2, r3 

       CALL jump_here

       CJE 5, 4, jump_start
       CJNE 5, 4, jump_start
       CJL 5, 4, jump_start
       CJLE 5, 4, jump_start
       CJG 5, 4, jump_start
       CJGE 5, 4, jump_start

       JMP jump_start

   jump_here:
       NOR 5, r2, r3
       NOR r1, r2, r3  
       RET
   */

   pub fn assembly(input: &str, output_debug: bool) {
       const INSTRUCTION_BIT_DEPTH: u8 = 4;
   
       let lines: Vec<&str> = input.lines().collect();
       let mut data_values: HashMap<String, u8> = HashMap::new();

       // PASS 1: collect labels
       let mut labels = HashMap::new();
       let mut address: u8 = 0;
       let mut last_label: Option<String> = None;

       let mut section = Section::Text;
       let mut text_end: u8 = 0;
       let mut data_org: Option<u8> = None;

       for line in &lines {
           match parse_line(line) {
               ParsedLine::Label(name) => {
                   labels.insert(name.clone(), address);
                   last_label = Some(name);
               }
               ParsedLine::Instruction(_) => {
                   address = address.checked_add(INSTRUCTION_BIT_DEPTH)
                  .unwrap_or_else(|| panic!("Address overflow! \nThe maximum address value is 255. \nCurrent address:{} + depth:{} > 255",address,INSTRUCTION_BIT_DEPTH));
               }
               ParsedLine::Directive(d) => {
                   match d {
                       Directive::Org(addr) => {
                           if section == Section::Data {
                               data_org = Some(addr);

                               if addr < text_end {
                                   panic!("Error: data section overlaps with text section");
                               }
                           }
                           address = addr;
                       }
                       Directive::Byte(value) => {
                           if section == Section::Data {
                               if let Some(label) = &last_label {
                                   data_values.insert(label.clone(), value);
                               } else {
                                   panic!(".byte without label in data section");
                               }
                           }
                           address = address.checked_add(1)
                          .unwrap_or_else(|| panic!("Address overflow! \nThe maximum address value is 255. \nCurrent address:{} + 1 > 255",address));
                       }
                       Directive::Text => {
                           section = Section::Text;
                       }
                       Directive::Data => {
                           section = Section::Data;
                           // фиксируем конец текста
                           text_end = address;
                           let data_start = match data_org {
                               Some(addr) => addr,
                               None => text_end,
                           };
                           if data_start < text_end {
                               panic!("Error: data section overlaps with text section");
                           }
                           address = data_start;
                       }
                   }
               }
               ParsedLine::Empty => {}
           }
       }

       // Debug label addresses
       if output_debug {
           println!("# label addresses:");
           for (key, value) in &labels {
               println!("# {}: {:03}", key, value); 
           }
           println!("# ------------------------------");
       }
       
       // PASS 2: encode instructions
       let mut output: Vec<u8> = vec![0; 256];
       let mut max_address: u8 = 0;
       address = 0;

       let mut section = Section::Text;
       let mut text_end: u8 = 0;
       let mut data_org: Option<u8> = None;
   
       for line in &lines {
           match parse_line(line) {
               ParsedLine::Instruction(instr) => {
                   let encoded = encode_instruction(instr, &labels, &data_values);

                   output[address as usize] = encoded.opcode;
                   output[(address + 1) as usize] = encoded.arg1;
                   output[(address + 2) as usize] = encoded.arg2;
                   output[(address + 3) as usize] = encoded.result;

                   address += INSTRUCTION_BIT_DEPTH;
                   max_address = max_address.max(address);
               }

               ParsedLine::Directive(d) => {
                   match d {
                       Directive::Org(addr) => {
                           if section == Section::Data {
                               data_org = Some(addr);
                           }
                           address = addr;
                       }
                       Directive::Byte(value) => {
                           output[address as usize] = value;
                           address += 1;
                           max_address = max_address.max(address);
                       }
                       Directive::Text => {
                           section = Section::Text;
                       }
                       Directive::Data => {
                           section = Section::Data;
                           text_end = address;
                           let data_start = match data_org {
                               Some(addr) => addr,
                               None => text_end,
                           };
                           if data_start < text_end {
                               panic!("Error: data section overlaps with text section");
                           }
                           address = data_start;
                       }
                   }
               }
               ParsedLine::Label(_) | ParsedLine::Empty => {}
           }
       }

       let used = max_address as usize;

       // show assembly code
       if !output_debug {
           for  (chunk_idx, inst) in output[..used].chunks(INSTRUCTION_BIT_DEPTH as usize).enumerate(){
               if inst.len() < 4 {
                   break;
               }
               println!("0b{:08b}", inst[0]);
               println!("0b{:08b}", inst[1]);
               println!("0b{:08b}", inst[2]);
               println!("0b{:08b}", inst[3]); 
               println!();
           }
       }

       // Debug
       if output_debug {
           println!("# Program size: {max_address} bytes");
           println!("# ------------------------------\n");
           println!("# bytes    |:addr|text instruction");
           for (chunk_idx, inst) in output[..used].chunks(INSTRUCTION_BIT_DEPTH as usize).enumerate(){
               if inst.len() < 4 {
                   break;
               }
               let base_addr = chunk_idx * INSTRUCTION_BIT_DEPTH as usize;

               let text_inst = decode(inst[0], inst[1], inst[2], inst[3]);

               println!("0b{:08b} #:{:03} |{}", inst[0],base_addr,text_inst);
               println!("0b{:08b} #:{:03}", inst[1],base_addr+1);
               println!("0b{:08b} #:{:03}", inst[2],base_addr+2);
               println!("0b{:08b} #:{:03}", inst[3],base_addr+3); 
               println!();
           }
       }
   }

   fn strip_comment(line: &str) -> &str {
       if let Some(pos) = line.find('#') {
           &line[..pos]
       } else {
           line
       }
   }

   fn parse_line(line: &str) -> ParsedLine {
       let line = line.trim();
       let code = strip_comment(line).trim();

       if code.is_empty() {
           return ParsedLine::Empty;
       }

       if code.ends_with(':') {
           let label = code.trim_end_matches(':').to_string();
           return ParsedLine::Label(label);
       }

       let tokens: Vec<&str> = code
           .split([' ', ','])
           .filter(|s| !s.is_empty())
           .collect();

       if tokens.is_empty() {
           return ParsedLine::Empty;
       }

       match tokens[0] {
           ".data" => ParsedLine::Directive(Directive::Data),
           ".text" => ParsedLine::Directive(Directive::Text),
           ".org" => {
               let value = parse_number(tokens[1]);
               ParsedLine::Directive(Directive::Org(value))
           }
           ".byte" => {
               let value = parse_number(tokens[1]);
               ParsedLine::Directive(Directive::Byte(value))
           },
           "MOV" => parse_mov(&tokens),
           "PUSH" => parse_push(&tokens),
           "POP" => parse_pop(&tokens),
           "JMP" => {
               let label = tokens[1].to_string();
               ParsedLine::Instruction(Instruction::Jmp { label })
           },
           "CJE"|"CJNE"|"CJL"|"CJLE"|"CJG"|"CJGE" => parse_cj(&tokens),
           "ADD" => parse_alu(&tokens, AluOp::Add),
           "SUB" => parse_alu(&tokens, AluOp::Sub),
           "AND" => parse_alu(&tokens, AluOp::And),
           "OR" => parse_alu(&tokens, AluOp::Or),
           "NOT" => parse_not(&tokens),
           "XOR" => parse_alu(&tokens, AluOp::Xor),
           "NAND" => parse_alu(&tokens, AluOp::Nand),
           "NOR" => parse_alu(&tokens, AluOp::Nor),
           "DIV" => parse_alu(&tokens, AluOp::Div),
           "CALL" => {
               let label = tokens[1].to_string();
               ParsedLine::Instruction(Instruction::Call { label })
           },
           "RET" => ParsedLine::Instruction(Instruction::Ret),
           _ => panic!("Unknown instruction: {}", tokens[0]),
       }
   }
   
   // MOV: 
   // * src регистров/RAM/INPUT/PC/Immediate Value 
   // * dest регистр/RAM/OUTPUT/PC
   fn parse_mov(tokens: &[&str]) -> ParsedLine {
       let src = parse_operand(tokens[1], 128);
       let dst = parse_source( tokens[2]);
       ParsedLine::Instruction(Instruction::Mov {
           src,
           dst
       })
   }

   fn parse_push(tokens: &[&str]) -> ParsedLine {
       let src = parse_operand(tokens[1], 128);
       ParsedLine::Instruction(Instruction::Push {src})
   }

   fn parse_pop(tokens: &[&str]) -> ParsedLine {
       let dst= {
           match tokens[1] {
               "r0" | "r1" | "r2" | "r3" | "r4" | "r5" |
               "pc" | "io" | "ram" => {
                   parse_source(tokens[1])
               }
               _ => {
                   panic!("Unknown source: {}", tokens[1]);
               }
           }
       };
       ParsedLine::Instruction(Instruction::Pop {
           dst, 
       })
   }
   
   fn parse_cj(tokens: &[&str]) -> ParsedLine {
       let a = parse_operand(tokens[1], 128);
       let b = parse_operand(tokens[2], 64);
       let label = tokens[3].to_string();
       let inst = {
           let inst = tokens[0];
           match inst {
               "CJE" => {0b00100000}, // 32 IF_EQUAL
               "CJNE" => {0b00100001},// 33 IF_NOT_EQUAL
               "CJL" => {0b00100010}, // 34 IF_LESS
               "CJLE" => {0b00100011},// 35 IF_LESS_OR_EQUAL 
               "CJG" => {0b00100100}, // 36 IF_GREATER
               "CJGE" => {0b00100101},// 37 IF_GREATER_OR_EQUAL
               _ => panic!("Unknown command:{}",inst)
           }
       };
       ParsedLine::Instruction(Instruction::Cj {a, b, label, inst })
   }

   fn parse_alu(tokens: &[&str], kind: AluOp) -> ParsedLine {
       let a = parse_operand(tokens[1], 128);
       let b = parse_operand(tokens[2], 64);
       let dst = parse_source(tokens[3]);
       match kind{
           AluOp::Add => {ParsedLine::Instruction(Instruction::Add {a, b, dst })},
           AluOp::Sub => {ParsedLine::Instruction(Instruction::Sub {a, b, dst })},
           AluOp::And => {ParsedLine::Instruction(Instruction::And { a, b, dst })},
           AluOp::Or => {ParsedLine::Instruction(Instruction::Or {a, b, dst })},
           AluOp::Xor => {ParsedLine::Instruction(Instruction::Xor {a, b, dst })},
           AluOp::Nand => {ParsedLine::Instruction(Instruction::Nand {a, b, dst })},
           AluOp::Nor => {ParsedLine::Instruction(Instruction::Nor {a, b, dst })},
           AluOp::Div => {ParsedLine::Instruction(Instruction::Div {a, b, dst })},
       }
   }

   fn parse_not(tokens: &[&str]) -> ParsedLine {
       let a = parse_operand(tokens[1], 128);
       let dst = parse_source(tokens[2]);
       ParsedLine::Instruction(Instruction::Not {a, dst })
   }

   fn parse_operand(s: &str, imm_flag: u8) -> SourceOperand {
       // [label] — адрес
       if s.starts_with('[') && s.ends_with(']') {
           let label = &s[1..s.len() - 1];
           return SourceOperand::new(imm_flag, Operand::IndirectAddr(label.to_string()));
       }

       // регистр
       match s {
           "r0" | "r1" | "r2" | "r3" | "r4" | "r5" |
           "pc" | "io" | "ram" => {
               return SourceOperand::new(0, Operand::Source(parse_source(s)));
           }
           _ => {}
       }

       // число
       if let Ok(num) = s.parse::<u8>() {
           return SourceOperand::new(imm_flag, Operand::Immediate(num));
       }

       // иначе это метка с константой
       SourceOperand::new(imm_flag, Operand::MemLabel(s.to_string()))
   }

   fn parse_source(name: &str) -> u8 {
       match name {
           "r0" => 0,
           "r1" => 1,
           "r2" => 2,
           "r3" => 3,
           "r4" => 4,
           "r5" => 5,
           "pc" => 6,
           "io" => 7,
           "ram" => 8,
           _ => panic!("Unknown register: {}", name),
       }
   }

   fn parse_number(s: &str) -> u8 {
       s.parse::<u8>().expect("Invalid number")
   }

   fn encode_src(src: SourceOperand, labels: &HashMap<String, u8>, data_values: &HashMap<String, u8>) -> u8 {
       match src.src {
           Operand::Source(s) => {s},
           Operand::Immediate(v) => {v},
           Operand::IndirectAddr(label) => {
               *labels.get(&label).expect("Unknown label")  
           } 
           Operand::MemLabel(label) => {
           // Если есть значение в data_values - берем его как immediate
               if let Some(val) = data_values.get(&label) {
                   *val
               } else {
                   // иначе используем адрес (если, например, jump)
                   *labels.get(&label).expect("Unknown label")
               } 
           }                 
       }
   }

   fn encode_instruction(
       instr: Instruction,
       labels: &HashMap<String, u8>,
       data_values: &HashMap<String, u8>
   ) -> EncodedInstruction {
       match instr {
           Instruction::Mov { src, dst } => EncodedInstruction {
               opcode: src.imm_flag|0b00001100,  
               arg1: encode_src(src, labels, data_values),
               arg2: 0,
               result: dst,
           },
           Instruction::Push { src } => EncodedInstruction {
               opcode: src.imm_flag|0b00010111,  
               arg1: encode_src(src, labels, data_values),
               arg2: 0,
               result: 0b00001000,
           },
           Instruction::Pop { dst } => EncodedInstruction {
               opcode: 0b00010101,  
               arg1: 0,
               arg2: 0,
               result: dst,
           },
           Instruction::Add { a, b, dst } => EncodedInstruction {
               opcode: a.imm_flag|b.imm_flag,
               arg1: encode_src(a, labels, data_values),
               arg2: encode_src(b, labels, data_values),
               result: dst,
           },
           Instruction::Sub { a, b, dst } => EncodedInstruction {
               opcode: a.imm_flag|b.imm_flag|0b00000001,
               arg1: encode_src(a, labels, data_values),
               arg2: encode_src(b, labels, data_values),
               result: dst,
           },   
           Instruction::And { a, b, dst } => EncodedInstruction {
               opcode: a.imm_flag|b.imm_flag|0b00000010,
               arg1: encode_src(a, labels, data_values),
               arg2: encode_src(b, labels, data_values),
               result: dst,
           },  
           Instruction::Or { a, b, dst } => EncodedInstruction {
               opcode: a.imm_flag|b.imm_flag|0b00000011,
               arg1: encode_src(a, labels, data_values),
               arg2: encode_src(b, labels, data_values),
               result: dst,
           },  
           Instruction::Not { a,dst } => EncodedInstruction {
               opcode: a.imm_flag|0b00000100,
               arg1: encode_src(a, labels, data_values),
               arg2: 0,
               result: dst,
           },  
           Instruction::Xor { a, b, dst } => EncodedInstruction {
               opcode: a.imm_flag|b.imm_flag|0b00000101,
               arg1: encode_src(a, labels, data_values),
               arg2: encode_src(b, labels, data_values),
               result: dst,
           },  
           Instruction::Nand { a, b, dst } => EncodedInstruction {
               opcode: a.imm_flag|b.imm_flag|0b00000110,
               arg1: encode_src(a, labels, data_values),
               arg2: encode_src(b, labels, data_values),
               result: dst,
           },  
           Instruction::Nor { a, b, dst } => EncodedInstruction {
               opcode: a.imm_flag|b.imm_flag|0b00000111,
               arg1: encode_src(a, labels, data_values),
               arg2: encode_src(b, labels, data_values),
               result: dst,
           },  
           Instruction::Div { a, b, dst } => EncodedInstruction {
               opcode: a.imm_flag|b.imm_flag|0b00010100,
               arg1: encode_src(a, labels, data_values),
               arg2: encode_src(b, labels, data_values),
               result: dst,
           },  
           Instruction::Call { label} => {
               let addr = *labels
                   .get(&label)
                   .expect("Unknown label");

               EncodedInstruction {
                   opcode: 0b00001000,
                   arg1: 0,
                   arg2: 0,
                   result: addr,
               }
           },
           Instruction::Jmp { label } => {
               let addr = *labels
                   .get(&label)
                   .expect("Unknown label");

               EncodedInstruction {
                   opcode: 0b10001100,
                   arg1: addr,
                   arg2: 0,
                   result: 0b00000110,
               }
           },
           Instruction::Cj { a, b, label, inst} => {
               let addr = *labels
                   .get(&label)
                   .expect("Unknown label");

               EncodedInstruction {
                   opcode:  a.imm_flag|b.imm_flag|inst|0b00100000,
                   arg1: encode_src(a, labels, data_values),
                   arg2: encode_src(b, labels, data_values),
                   result: addr,
               }
           },
           Instruction::Ret => EncodedInstruction {
               opcode: 0b00010000,
               arg1: 0,
               arg2: 0,
               result: 0,
           },
       }
   }

   pub mod disassembly {
       fn reg_name(id: u8) -> &'static str {
           match id {
               0 => "r0",
               1 => "r1",
               2 => "r2",
               3 => "r3",
               4 => "r4",
               5 => "r5",
               6 => "pc",
               7 => "io",
               8 => "ram",
               _ => "unk",
           }
       }

       fn decode_operand(flag: bool, value: u8) -> String {
           if flag {
               value.to_string()
           } else {
               reg_name(value).to_string()
           }
       }

       pub fn decode(opcode: u8, arg1: u8, arg2: u8, result: u8) -> String {
           let ai = opcode & 0b10000000 != 0;
           let bi = opcode & 0b01000000 != 0;
           let base = opcode & 0b00111111;

           let a = decode_operand(ai, arg1);
           let b = decode_operand(bi, arg2);
           let dst = reg_name(result);

           match base {
               0b00000000 => format!("ADD {}, {}, {}", a, b, dst),
               0b00000001 => format!("SUB {}, {}, {}", a, b, dst),
               0b00000010 => format!("AND {}, {}, {}", a, b, dst),
               0b00000011 => format!("OR {}, {}, {}", a, b, dst),
               0b00000100 => format!("NOT {}, {}", a, dst),
               0b00000101 => format!("XOR {}, {}, {}", a, b, dst),
               0b00000110 => format!("NAND {}, {}, {}", a, b, dst),
               0b00000111 => format!("NOR {}, {}, {}", a, b, dst),
               0b00001100 => {
                   let src = decode_operand(ai, arg1);
                   let dst = reg_name(result);
                   format!("MOV {}, {}", src, dst)
               },
               0b00010111 => {
                   let src = decode_operand(ai, arg1);
                   format!("PUSH :{}", src)
               },
               0b00010101 => {
                   let dst = reg_name(result);
                   format!("POP {}", dst)
               },
               0b00010100 => format!("DIV {}, {}, {}", a, b, dst),
               0b00001000 => {
                   format!("CALL :{:03}", result)
               },
               0b00010000 => "RET".to_string(),
               0b00100000 => format!("CJE {}, {}, :{:03}", a, b, result),
               0b00100001 => format!("CJNE {}, {}, :{:03}", a, b, result),
               0b00100010 => format!("CJL {}, {}, :{:03}", a, b, result),
               0b00100011 => format!("CJLE {}, {}, :{:03}", a, b, result),
               0b00100100 => format!("CJG {}, {}, :{:03}", a, b, result),
               0b00100101 => format!("CJGE {}, {}, :{:03}", a, b, result),
               _ => format!(
                   "Unknown opcode={:08b} arg1={} arg2={} dst={:08}",
                   opcode, arg1, arg2, result
               ),
           }
       }
   }
}