diff --git a/bench/programs_rust/Cargo.toml b/bench/programs_rust/Cargo.toml index 968f0ce..ce01597 100644 --- a/bench/programs_rust/Cargo.toml +++ b/bench/programs_rust/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["fpga-lib", "fpga-lib-macros", "hello-rust"] +members = ["fpga-lib", "fpga-lib-macros", "hello-rust", "game-2048"] [profile.release] panic = "abort" diff --git a/bench/programs_rust/fpga-lib/src/interface.rs b/bench/programs_rust/fpga-lib/src/interface.rs index b6e0863..8636949 100644 --- a/bench/programs_rust/fpga-lib/src/interface.rs +++ b/bench/programs_rust/fpga-lib/src/interface.rs @@ -1,28 +1,21 @@ pub struct Buttons {} -impl Buttons -{ - pub unsafe fn get_button(button_id: u8) -> bool - { - unsafe { *LED_IP_ADDRESS >> (button_id) & 0b1 == 0b1 } +impl Buttons { + pub unsafe fn get_button(button_id: u8) -> bool { + unsafe { ((*LED_IP_ADDRESS) >> button_id) & 0b1 == 0b1 } } } pub struct Leds {} -impl Leds -{ - pub unsafe fn set_led(led_id: u8, state: bool) - { - if state - { - unsafe { *LED_IP_ADDRESS |= (state as u32) << (led_id + 4) } - } - else - { - unsafe { *LED_IP_ADDRESS &= !((state as u32) << (led_id + 4)) } +impl Leds { + pub unsafe fn set_led(led_id: u8, state: bool) { + if state { + unsafe { *LED_IP_ADDRESS |= (state as u64) << (led_id + 4) } + } else { + unsafe { *LED_IP_ADDRESS &= !((state as u64) << (led_id + 4)) } } } } -pub const LED_IP_ADDRESS: *mut u32 = 0x30000000 as *mut u32; +pub const LED_IP_ADDRESS: *mut u64 = 0x30000000 as *mut u64; diff --git a/bench/programs_rust/fpga-lib/src/lib.rs b/bench/programs_rust/fpga-lib/src/lib.rs index f1124dc..929de86 100644 --- a/bench/programs_rust/fpga-lib/src/lib.rs +++ b/bench/programs_rust/fpga-lib/src/lib.rs @@ -8,10 +8,11 @@ pub mod vga; macro_rules! panic_handler { () => { #[panic_handler] - fn panic(_info: &core::panic::PanicInfo) -> ! - { - loop - {} + fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { + $crate::vga::VGA::draw_string(0, 0, "PANIC !", $crate::vga::WHITE); + } + loop {} } }; } diff --git a/bench/programs_rust/fpga-lib/src/vga.rs b/bench/programs_rust/fpga-lib/src/vga.rs index 2579f2e..a8a7d29 100644 --- a/bench/programs_rust/fpga-lib/src/vga.rs +++ b/bench/programs_rust/fpga-lib/src/vga.rs @@ -1,3 +1,5 @@ +use core::ptr::write_bytes; + use fpga_lib_macros::include_font_plate; pub const VGA_ADDRESS: *mut Color = 0x80000000 as *mut Color; @@ -8,10 +10,8 @@ pub const HEIGHT: usize = 240; #[derive(Clone, Copy)] pub struct Color(pub(crate) u8); -impl Color -{ - pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self - { +impl Color { + pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { Self(((r >> 6) << 6) | ((g >> 5) << 3) | (b >> 5)) } } @@ -26,12 +26,10 @@ pub const BLUE: Color = Color::from_rgb(0, 0, 255); pub struct VGA {} -impl VGA -{ +impl VGA { /// # Safety /// `x` must be less than `WIDTH` and `y` must be less than `HEIGHT` - pub unsafe fn write_pixel_unsafe(x: u16, y: u16, color: Color) - { + pub unsafe fn write_pixel_unsafe(x: u16, y: u16, color: Color) { let pixel_index = x as usize + y as usize * WIDTH; let word_index = pixel_index / 4; // Get index of the word in which the pixel is let byte_index = pixel_index % 4; // Get index of the byte within the word for the pixel @@ -41,14 +39,10 @@ impl VGA /// # Safety /// `x` must be less than `WIDTH` and `y` must be less than `HEIGHT` - pub unsafe fn draw_char(x: u16, y: u16, c: char, color: Color) - { - let c = if (c as u8 > b'~') || ((c as u8) < b' ') - { + pub unsafe fn draw_char(x: u16, y: u16, c: char, color: Color) { + let c = if (c as u8 > b'~') || ((c as u8) < b' ') { b'/' - b' ' - } - else - { + } else { c as u8 - b' ' }; @@ -56,10 +50,8 @@ impl VGA let char_x = (c as usize % 32) * FONT_WIDTH; let char_y = (c as usize / 32) * FONT_HEIGHT; - for i in 0..(FONT_WIDTH as u16) - { - for j in 0..(FONT_HEIGHT as u16) - { + for i in 0..(FONT_WIDTH as u16) { + for j in 0..(FONT_HEIGHT as u16) { let xx = x + i; let yy = y + j; @@ -73,14 +65,10 @@ impl VGA } } - pub unsafe fn draw_char_bg(x: u16, y: u16, c: char, color: Color, bg_color: Color) - { - let c = if (c as u8 > b'~') || ((c as u8) < b' ') - { + pub unsafe fn draw_char_bg(x: u16, y: u16, c: char, color: Color, bg_color: Color) { + let c = if (c as u8 > b'~') || ((c as u8) < b' ') { b'/' - b' ' - } - else - { + } else { c as u8 - b' ' }; @@ -88,21 +76,15 @@ impl VGA let char_x = (c as usize % 32) * FONT_WIDTH; let char_y = (c as usize / 32) * FONT_HEIGHT; - for i in 0..(FONT_WIDTH as u16) - { - for j in 0..(FONT_HEIGHT as u16) - { + for i in 0..(FONT_WIDTH as u16) { + for j in 0..(FONT_HEIGHT as u16) { let xx = x + i; let yy = y + j; - if xx < (WIDTH as u16) && yy < (HEIGHT as u16) - { - if unsafe { Self::font_plate_index(char_x as u16 + i, char_y as u16 + j) } - { + if xx < (WIDTH as u16) && yy < (HEIGHT as u16) { + if unsafe { Self::font_plate_index(char_x as u16 + i, char_y as u16 + j) } { unsafe { Self::write_pixel_unsafe(xx, yy, color) } - } - else - { + } else { unsafe { Self::write_pixel_unsafe(xx, yy, bg_color) } } } @@ -112,18 +94,18 @@ impl VGA /// # Safety /// The text must have a length that can fit within a `u16` - pub unsafe fn draw_string(x: u16, y: u16, str: &str, color: Color) - { + pub unsafe fn draw_string(x: u16, y: u16, str: &str, color: Color) { str.chars().enumerate().for_each(|(i, c)| unsafe { Self::draw_char(x + i as u16 * (FONT_WIDTH as u16), y, c as char, color) }); } + pub fn clear_screen(color: Color) { + unsafe { write_bytes(VGA_ADDRESS, color.0, WIDTH * HEIGHT) }; + } - fn digit_count(mut val: u64) -> u64 - { + fn digit_count(mut val: u64) -> u64 { let mut i = 0; - while val != 0 - { + while val != 0 { val /= 10; i += 1; } @@ -131,17 +113,14 @@ impl VGA i } - pub unsafe fn draw_u64(x: u16, y: u16, mut val: u64, color: Color, bg_color: Color) - { - if val == 0 - { + pub unsafe fn draw_u64(x: u16, y: u16, mut val: u64, color: Color, bg_color: Color) { + if val == 0 { unsafe { Self::draw_char_bg(x, y, '0', color, bg_color) } return; } let digit_count = Self::digit_count(val) - 1; let mut i = digit_count as u16; - while val != 0 - { + while val != 0 { let digit = val % 10; val /= 10; @@ -160,8 +139,7 @@ impl VGA } } - unsafe fn font_plate_index(x: u16, y: u16) -> bool - { + unsafe fn font_plate_index(x: u16, y: u16) -> bool { let pixel_index = (y as usize) * FONTPLATE_WIDTH + (x as usize); let byte_index = pixel_index / 8; let bit_index = pixel_index % 8; diff --git a/bench/programs_rust/game-2048/.gitignore b/bench/programs_rust/game-2048/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/bench/programs_rust/game-2048/.gitignore @@ -0,0 +1 @@ +/target diff --git a/bench/programs_rust/game-2048/Cargo.toml b/bench/programs_rust/game-2048/Cargo.toml new file mode 100644 index 0000000..81d9a5d --- /dev/null +++ b/bench/programs_rust/game-2048/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "game-2048" +version = "0.1.0" +edition = "2024" + +[dependencies] +fpga-lib = { path = "../fpga-lib" } +rand = {version = "0.9.2", default-features = false, features = ["small_rng"]} diff --git a/bench/programs_rust/game-2048/README.md b/bench/programs_rust/game-2048/README.md new file mode 100644 index 0000000..d9f1714 --- /dev/null +++ b/bench/programs_rust/game-2048/README.md @@ -0,0 +1,3 @@ +# 2048 + +A terminal version of the 2048 game. diff --git a/bench/programs_rust/game-2048/src/main.rs b/bench/programs_rust/game-2048/src/main.rs new file mode 100644 index 0000000..79b1656 --- /dev/null +++ b/bench/programs_rust/game-2048/src/main.rs @@ -0,0 +1,332 @@ +#![no_std] +#![no_main] +#![allow(incomplete_features)] +#![feature(generic_const_exprs)] + +use core::{ + iter::Cloned, + mem::MaybeUninit, + ops::{Index, IndexMut}, +}; + +use fpga_lib::{ + panic_handler, + vga::{Color, BLACK, FONT_HEIGHT, FONT_WIDTH, RED, WHITE}, +}; +use rand::{seq::IndexedRandom, Rng, SeedableRng}; + +const fn get_color(x: u64) -> Color { + match x { + 2 => Color::from_rgb(238, 228, 222), + 4 => Color::from_rgb(237, 224, 200), + 8 => Color::from_rgb(242, 177, 120), + 16 => Color::from_rgb(245, 149, 99), + 32 => Color::from_rgb(246, 124, 95), + 64 => Color::from_rgb(246, 94, 59), + 128 => Color::from_rgb(237, 207, 114), + 256 => Color::from_rgb(237, 204, 97), + 512 => Color::from_rgb(237, 200, 80), + 1024 => Color::from_rgb(237, 197, 63), + 2048 => Color::from_rgb(237, 194, 46), + 4096 => Color::from_rgb(238, 102, 106), + 8192 => Color::from_rgb(238, 78, 90), + 16384 => Color::from_rgb(241, 65, 65), + 32768 => Color::from_rgb(114, 178, 212), + 65536 => Color::from_rgb(88, 159, 226), + 131072 => Color::from_rgb(23, 129, 204), + _ => Color::from_rgb(255, 255, 255), + } +} + +pub struct NZip { + iters: [T; N], +} + +impl Iterator for NZip { + type Item = [T::Item; N]; + + fn next(&mut self) -> Option { + let mut res = MaybeUninit::<[T::Item; N]>::uninit(); + for (cpt, iter) in self.iters.iter_mut().enumerate() { + unsafe { *(res.as_mut_ptr() as *mut T::Item).add(cpt) = iter.next()? }; + } + Some(unsafe { res.assume_init() }) + } +} + +impl DoubleEndedIterator for NZip { + fn next_back(&mut self) -> Option { + let mut res = MaybeUninit::<[T::Item; N]>::uninit(); + for (cpt, iter) in self.iters.iter_mut().enumerate() { + unsafe { *(res.as_mut_ptr() as *mut T::Item).add(cpt) = iter.next_back()? }; + } + Some(unsafe { res.assume_init() }) + } +} + +#[derive(Clone)] +pub struct Game +where + [(); SIZE * SIZE]:, +{ + grid: [u64; SIZE * SIZE], + rng: rand::rngs::SmallRng, + score: u64, +} + +impl Index<(usize, usize)> for Game +where + [(); SIZE * SIZE]:, +{ + type Output = u64; + + fn index(&self, index: (usize, usize)) -> &Self::Output { + &self.grid[index.1 * SIZE + index.0] + } +} +impl IndexMut<(usize, usize)> for Game +where + [(); SIZE * SIZE]:, +{ + fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output { + &mut self.grid[index.1 * SIZE + index.0] + } +} + +impl Game +where + [(); SIZE * SIZE]:, +{ + pub fn new() -> Self { + Self { + grid: [0; SIZE * SIZE], + rng: rand::rngs::SmallRng::seed_from_u64(9543223934), + score: 0, + } + } + pub fn init() -> Self { + let mut g = Self::new(); + g.add(); + g.add(); + g + } + pub fn reset(&mut self) { + *self = Self::new() + } + pub fn add(&mut self) { + let mut pos = [(0, 0); SIZE * SIZE]; + let mut cpt = 0; + for y in 0..SIZE { + for x in 0..SIZE { + if self[(x, y)] == 0 { + pos[cpt] = (x, y); + cpt += 1; + } + } + } + let pos = *pos[0..cpt].choose(&mut self.rng).unwrap(); + self[pos] = (self.rng.random_range(0..=1) + 1) * 2; + } + pub fn lost(&self) -> bool { + let mut copy = self.clone(); + !(copy.left() || copy.down() || copy.right() || copy.up()) + } + fn rotate(&mut self) { + let mut out = self.grid; + + for i in 0..SIZE { + for j in 0..SIZE { + out[j * SIZE + (SIZE - i - 1)] = self.grid[i * SIZE + j]; + } + } + + self.grid = out; + } + + fn rotate_back(&mut self) { + let mut out = self.grid; + + for i in 0..SIZE { + for j in 0..SIZE { + out[(SIZE - j - 1) * SIZE + i] = self.grid[i * SIZE + j]; + } + } + + self.grid = out; + } + pub fn left(&mut self) -> bool { + let mut changed = false; + for y in 0..SIZE { + let mut index = 0; + let mut fixed = false; + let mut zero_found = false; + for x in 0..SIZE { + if self[(x, y)] == 0 { + zero_found = true; + continue; + } else if zero_found { + changed = true; + } + if index != 0 && self[(index - 1, y)] == (self[(x, y)]) && !fixed { + self[(index - 1, y)] = self[(x, y)] * 2; + self[(x, y)] = 0; + self.score += self[(index - 1, y)]; + fixed = true; + changed = true; + } else { + self[(index, y)] = self[(x, y)]; + if index != x { + self[(x, y)] = 0; + } + fixed = false; + index += 1; + } + } + } + changed + } + pub fn down(&mut self) -> bool { + self.rotate(); + let res = self.left(); + self.rotate_back(); + res + } + pub fn right(&mut self) -> bool { + self.rotate(); + self.rotate(); + let res = self.left(); + self.rotate_back(); + self.rotate_back(); + res + } + pub fn up(&mut self) -> bool { + self.rotate_back(); + let res = self.left(); + self.rotate(); + res + } + pub fn run(&mut self) -> ! { + let mut buttons = [false; 4]; + let funcs = [Self::left, Self::right, Self::up, Self::down]; + loop { + let mut lost = false; + self.draw(); + loop { + for direction in 0..4 { + let button = + unsafe { fpga_lib::interface::Buttons::get_button(direction as u8) }; + if button && !buttons[direction] { + buttons[direction] = true; + + if funcs[direction](self) { + self.add(); + self.draw(); + if self.lost() { + lost = true; + break; + } + } + } else if !button && buttons[direction] { + buttons[direction] = false + } + } + if lost { + unsafe { + fpga_lib::vga::VGA::draw_string( + OFFSET, + OFFSET + (SIZE as u16) * BLOCK_SIZE + FONT_HEIGHT as u16 + 10, + "Game Over !", + RED, + ) + }; + self.reset(); + while !(0..4).any(|direction| unsafe { + fpga_lib::interface::Buttons::get_button(direction as u8) + }) {} + break; + } + } + } + } +} + +impl Default for Game +where + [(); SIZE * SIZE]:, +{ + fn default() -> Self { + Self::new() + } +} + +impl Game +where + [(); SIZE * SIZE]:, +{ + fn draw(&self) { + fpga_lib::vga::VGA::clear_screen(BLACK); + for i in 0..SIZE as u16 + 1 { + for x in 0..BLOCK_SIZE * SIZE as u16 { + unsafe { + fpga_lib::vga::VGA::write_pixel_unsafe( + OFFSET + x, + OFFSET + i * BLOCK_SIZE, + WHITE, + ) + }; + unsafe { + fpga_lib::vga::VGA::write_pixel_unsafe( + OFFSET + i * BLOCK_SIZE, + OFFSET + x, + WHITE, + ) + }; + } + } + for y in 0..SIZE { + for x in 0..SIZE { + if self[(x, y)] != 0 { + unsafe { + fpga_lib::vga::VGA::draw_u64( + OFFSET + x as u16 * BLOCK_SIZE + BLOCK_SIZE / 2 + - (fpga_lib::vga::FONT_WIDTH as u16 + * (self[(x, y)].ilog10() as u16 + 1)) + / 2, + OFFSET + y as u16 * BLOCK_SIZE + BLOCK_SIZE / 2 + - fpga_lib::vga::FONT_HEIGHT as u16 / 2, + self[(x, y)], + get_color(self[(x, y)]), + BLACK, + ) + }; + } + } + } + unsafe { + fpga_lib::vga::VGA::draw_string( + OFFSET, + OFFSET + (SIZE as u16) * BLOCK_SIZE + 5, + "Score :", + WHITE, + ); + fpga_lib::vga::VGA::draw_u64( + OFFSET + FONT_WIDTH as u16 * 8, + OFFSET + (SIZE as u16) * BLOCK_SIZE + 5, + self.score, + WHITE, + BLACK, + ); + } + } +} + +panic_handler! {} + +#[unsafe(no_mangle)] +pub extern "C" fn main() -> ! { + let mut g = Game::<4>::init(); + g.run() +} + +const BLOCK_SIZE: u16 = 40; +const OFFSET: u16 = 20; diff --git a/common/ZzTop.xdc b/common/ZzTop.xdc index 688288c..e4e90ef 100644 --- a/common/ZzTop.xdc +++ b/common/ZzTop.xdc @@ -20,7 +20,7 @@ set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { io_swi set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { io_push[0] }]; #IO_L20N_T3_34 Sch=BTN0 set_property -dict { PACKAGE_PIN P16 IOSTANDARD LVCMOS33 } [get_ports { io_push[1] }]; #IO_L24N_T3_34 Sch=BTN1 set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { io_push[2] }]; #IO_L18P_T2_34 Sch=BTN2 -set_property -dict { PACKAGE_PIN Y16 IOSTANDARD LVCMOS33 } [get_ports { reset }]; #IO_L7P_T1_34 Sch=BTN3 +set_property -dict { PACKAGE_PIN Y16 IOSTANDARD LVCMOS33 } [get_ports { io_push[3] }]; #IO_L7P_T1_34 Sch=BTN3 # LEDs diff --git a/src/main/scala/projet/LedIp.scala b/src/main/scala/projet/LedIp.scala index 09e6492..36e9216 100644 --- a/src/main/scala/projet/LedIp.scala +++ b/src/main/scala/projet/LedIp.scala @@ -10,7 +10,7 @@ class LedIp extends Module { val x31 = Input(UInt(64.W)) val switch = Input(UInt(4.W)) val led = Output(UInt(4.W)) - val button = Input(UInt(3.W)) + val button = Input(UInt(4.W)) val bus = new BusInterface }) @@ -27,7 +27,7 @@ class LedIp extends Module { when(io.bus.be.asUInt.orR) { // Écriture dans le registre des leds internes dinReg := io.bus.wdata }.otherwise { // Lecture des boutons poussoirs, pas de débounce car lus par le soft - btnReg := Fill(29, 0.U) ## io.button + btnReg := Fill(28, 0.U) ## io.button } } diff --git a/src/main/scala/projet/ZzTop.scala b/src/main/scala/projet/ZzTop.scala index 1c730c4..c1ea765 100644 --- a/src/main/scala/projet/ZzTop.scala +++ b/src/main/scala/projet/ZzTop.scala @@ -7,7 +7,7 @@ class ZzTop(file: String = "", sim: Boolean = true) extends Module { // Interface debug minimale val switch = Input(UInt(4.W)) val led = Output(UInt(4.W)) - val push = Input(UInt(3.W)) + val push = Input(UInt(4.W)) // Debug simu pour ne pas s'embeter avec le switch dans le testbench val x31 = if (sim) Some(Output(UInt(64.W))) else None val valid_x31 = if (sim) Some(Output(Bool())) else None