├── .gitmodules ├── LICENSE ├── README.md ├── docs ├── building.md ├── mc1-diagram.png ├── mc1-diagram.svg ├── mc1-logo.png ├── mc1-logo.svg └── screenshots │ ├── mc1-demo.jpg │ └── raytrace.jpg └── src ├── .clang-format ├── .gitignore ├── README.md ├── rom ├── Makefile ├── console.hpp ├── crt0.s ├── fp32.hpp ├── link.ld ├── main.cpp ├── media │ ├── boot-splash.png │ └── boot-splash.svg ├── mosaic.hpp ├── out │ └── .gitignore ├── rom.vhd.in ├── splash.hpp └── tools │ └── raw2vhd.py ├── rtl ├── bit_synchronizer.vhd ├── de0_cv │ ├── constraints.sdc │ ├── pll.vhd │ ├── pll_intel.v │ └── toplevel.vhd ├── de10_lite │ ├── pll.vhd │ └── toplevel.vhd ├── dither.vhd ├── fifo.vhd ├── mc1.vhd ├── mmio.vhd ├── mmio_types.vhd ├── prng.vhd ├── ps2_keyboard.vhd ├── ps2_receiver.vhd ├── ram_true_dual_port.vhd ├── ram_true_dual_port_vhdl93.vhd ├── reset_conditioner.vhd ├── reset_stabilizer.vhd ├── sdram.vhd ├── synchronizer.vhd ├── vid_blend.vhd ├── vid_palette.vhd ├── vid_pix_prefetch.vhd ├── vid_pixel.vhd ├── vid_raster.vhd ├── vid_regs.vhd ├── vid_types.vhd ├── vid_vcpp.vhd ├── vid_vcpp_stack.vhd ├── video.vhd ├── video_layer.vhd ├── vram.vhd ├── wb_crossbar_2x4.vhd └── xram_sdram.vhd ├── run.py └── test ├── dual-gradients.vcp ├── dummy_tb.vhd ├── mc1-defines.vcp ├── mc1_tb.vhd ├── pal24to32.py ├── pal32to24.py ├── sdram_model.vhd ├── test-image-320x180-pal8.raw ├── test-image-320x180-pal8.raw.pal ├── test-image-320x180-pal8.vcp ├── test-image-640x360-pal8.raw ├── test-image-640x360-pal8.raw.pal ├── test-image-640x360-pal8.vcp ├── vid_vcpp_tb.vhd └── video_tb.vhd /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/mrisc32-a1"] 2 | path = src/mrisc32-a1 3 | url = git@github.com:mrisc32/mrisc32-a1.git 4 | [submodule "src/rom/selftest"] 5 | path = src/selftest 6 | url = git@github.com:mrisc32/mrisc32-selftest.git 7 | [submodule "src/mc1-sdk"] 8 | path = src/mc1-sdk 9 | url = git@github.com:mrisc32/mc1-sdk.git 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2020 Marcus Geelnard 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In no event will the 4 | authors be held liable for any damages arising from the use of this software. 5 | 6 | Permission is granted to anyone to use this software for any purpose, including commercial 7 | applications, and to alter it and redistribute it freely, subject to the following restrictions: 8 | 9 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote 10 | the original software. If you use this software in a product, an acknowledgment in the 11 | product documentation would be appreciated but is not required. 12 | 13 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 14 | being the original software. 15 | 16 | 3. This notice may not be removed or altered from any source distribution. 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## This repo has moved to: https://gitlab.com/mrisc32/mc1 2 | 3 | ![MC1 logo](docs/mc1-logo.png) 4 | 5 | MC1 is a compact computer intended for FPGA:s, based on the [MRISC32-A1](https://github.com/mrisc32/mrisc32-a1) soft microprocessor. 6 | 7 | The architecture is portable and configurable to fit a wide range of FPGA devices and boards. 8 | 9 | ## Architecture 10 | 11 | ![MC1 diagram](docs/mc1-diagram.png) 12 | 13 | The artchitecture is based around a tightly integrated and flexible video subsystem, which shares memory with the CPU. 14 | 15 | The shared video RAM (VRAM) has two memory ports - one dedicated to the CPU and one dedicated to the video logic. This enables single-cycle access for both, and the CPU and the video logic can run at different frequencies. 16 | 17 | ### CPU 18 | 19 | The CPU is a full MRISC32-A1, with support for floating point and vector operations. 20 | 21 | Connected to the CPU are on-chip ROM and RAM memories. The ROM holds the boot code (in lack of external flash memory or similar, it contains the entire program/system code). Furthermore, external RAM (such as DRAM or SRAM) can be accessed, provided that a suitable Wishbone compatible memory controller is added (depending on which FPGA board you are targeting). 22 | 23 | ### Video 24 | 25 | [![MC1 Demo](docs/screenshots/mc1-demo.jpg)](https://vimeo.com/494653227 "MC1 Demo") 26 | 27 | The video logic produces 24-bit RGB output and horizontal and vertical sync signals in 1920x1080 (1080p), suitable for VGA, DVI and HDMI interfaces, for instance. 28 | 29 | One key feature of the video logic is that it has a programmable video control processor that runs in sync with the raster signals, making it possible to get the most out of limited memory resources while offloading the CPU for certain tasks. 30 | 31 | Examples of things that a video control program can accomplish are: 32 | * Control things like the vertical and horizontal resolution and the color mode on a per-line basis. 33 | * Control the color palette on a per-line basis. 34 | * Vertical and horizontal mirroring. 35 | * Vertical and horizontal repeating of patterns. 36 | 37 | This means that it is possible to mix resolutions and color modes in a single frame, and you can fill the screen with rich colors and high resolution content even with very limited video RAM resources. 38 | 39 | For more details, see the [MC1 SDK documentation](https://github.com/mrisc32/mc1-sdk). 40 | 41 | ### I/O 42 | 43 | Primitive I/O, such as reading buttons and switches and writing to leds and seven-segment displays, is provided as memory mapped I/O (see the [MC1 SDK documentation](https://github.com/mrisc32/mc1-sdk)), directly accessible for the CPU. 44 | 45 | Keyboard input is provided for boards that have a PS/2 connector, and SD card I/O is provided for boards with an SD card port. 46 | 47 | ## Operating system 48 | 49 | No operating system is planned at this point. There will be libraries of helper routined (e.g. for I/O and timing) that can be statically linked to MC1 binaries. 50 | 51 | The ROM can load a program binary into RAM, and all system control is transfered to the loaded program (so it's essentially a two-stage boot). 52 | 53 | See: [MC1 SDK documentation](https://github.com/mrisc32/mc1-sdk). 54 | 55 | ## Planned features 56 | 57 | The following things are not yet implemented, but planned: 58 | 59 | * CPU: 60 | * Interrupt signals from the video logic (e.g. VSYNC), once interrupt logic has been added to the MRISC32. 61 | * Audio: 62 | * Some sort of high quality audio DMA, integrated with the video logic (sharing the same RAM read port). 63 | * Possibly some sort of waveform synthesis for low RAM systems. 64 | * Memory: 65 | * Support for off-chip RAM (e.g. DRAM or SRAM) - probably with an on-chip L2 cache. 66 | * I/O: 67 | * Mouse input. 68 | 69 | ## Build instructions 70 | 71 | See: [Building the MC1](docs/building.md). 72 | -------------------------------------------------------------------------------- /docs/building.md: -------------------------------------------------------------------------------- 1 | # Building the MC1 2 | 3 | These build instructions assume that you are running some flavor of Linux. 4 | 5 | ## Prerequisites 6 | 7 | Start by cloning this repository, and update all submodules: 8 | 9 | ```bash 10 | git submodule update --init --recursive 11 | ``` 12 | 13 | You also need a working installation of the [MRISC32 GNU toolchain](https://github.com/mrisc32/mrisc32-gnu-toolchain) (in your PATH). 14 | 15 | ## Build the ROM 16 | 17 | First build the MC1 SDK tools, according to the instructions in the tools README (`src/mc1-sdk/tools/README.md`). 18 | 19 | The ROM source code is located in [src/rom](../src/rom/). When building the ROM, a VHDL file is created that is used when building the VHDL design. 20 | 21 | ```bash 22 | cd src/rom 23 | make -j20 24 | ``` 25 | 26 | If a change is made to the ROM source code, this step needs to be repeated before re-building the VHDL design. 27 | 28 | ## Synthesizing the VHDL design 29 | 30 | To synthesize the design for a target FPGA you need: 31 | 32 | * A tool that understands VHDL 2008. 33 | * A toplevel design file, including: 34 | * Interfaces for things like LED:s, SD-card and keyboard. 35 | * The MC1 & MRISC32-A1 configuration. 36 | * Optionally device specific entities, e.g. PLL:s. 37 | 38 | Currently, toplevel designs are provided for the following boards: 39 | 40 | * Terasic DE0-CV (Cyclone V 5CEBA4F23C7N). 41 | * Terasic DE10-Lite (MAX 10 10M50DAF484C7G). 42 | 43 | ### Intel Quartus (DE0-CV, DE10-Lite) 44 | 45 | Note: This has been tested with Quartus 19.1. 46 | 47 | #### Create project 48 | 49 | Create a new empty project in Quartus using the Project Wizard. 50 | 51 | Add the following files to the project: 52 | 53 | * All files in **`src/rtl`** 54 | * Except: ~~`ram_true_dual_port.vhd`~~ 55 | * All files in **`src/rtl/de0_cv`** *or* **`src/rtl/de10_lite`** 56 | * All files in the subfolders of **`src/mrisc32-a1/rtl`** 57 | * Except: ~~`toplevel.vhd`~~, ~~`fpu/fpu_test_gen.cpp`~~ 58 | * **`src/rom/out/rom.vhd`** 59 | 60 | Select the right FPGA device for your board. 61 | 62 | When the project has been created, make the following project settings: 63 | 64 | * General: Top-level entity: **toplevel** 65 | * Files: Mark all `mrisc32-a1/*` files, click Properties, enter **mrisc32** in the Library field, press OK, and Apply. 66 | * Compiler Settings: Optimization mode = **Performance (aggressive)** 67 | * Compiler Settings > VHDL Input: VHDL version = **VHDL 2008** 68 | 69 | #### Compile & program 70 | 71 | * Compile Design 72 | * Program Device 73 | -------------------------------------------------------------------------------- /docs/mc1-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrisc32/mc1/0ab36c80157da33d715ded4f562b379ff9cfe000/docs/mc1-diagram.png -------------------------------------------------------------------------------- /docs/mc1-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrisc32/mc1/0ab36c80157da33d715ded4f562b379ff9cfe000/docs/mc1-logo.png -------------------------------------------------------------------------------- /docs/mc1-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 42 | 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 54 | 59 | 62 | 65 | 68 | 74 | 81 | 87 | 88 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /docs/screenshots/mc1-demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrisc32/mc1/0ab36c80157da33d715ded4f562b379ff9cfe000/docs/screenshots/mc1-demo.jpg -------------------------------------------------------------------------------- /docs/screenshots/raytrace.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrisc32/mc1/0ab36c80157da33d715ded4f562b379ff9cfe000/docs/screenshots/raytrace.jpg -------------------------------------------------------------------------------- /src/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Chromium 4 | AccessModifierOffset: -2 5 | AllowShortFunctionsOnASingleLine: None 6 | BinPackArguments: false 7 | ColumnLimit: 100 8 | IncludeCategories: 9 | - Regex: '^(<(base/|cache/|config/|sys/|wrappers/).+\.hpp)' 10 | Priority: 2 11 | - Regex: '^<(.*\.h>|c)' 12 | Priority: 3 13 | - Regex: '^<.*' 14 | Priority: 4 15 | - Regex: '^"' 16 | Priority: 1 17 | - Regex: '.*' 18 | Priority: 5 19 | SortIncludes: true 20 | ... 21 | 22 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | vunit_out 2 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # HDL source code 2 | 3 | ## RTL 4 | 5 | The [rtl](./rtl) folder contains the RTL of the system. 6 | 7 | 8 | ## Test benches 9 | 10 | The tests are located in the [test](./test) folder and use 11 | [VUnit](https://vunit.github.io/): 12 | 13 | ```bash 14 | $ pip3 install --user vunit_hdl 15 | $ ./run.py 16 | ``` 17 | 18 | In order to run the tests, you need a VHDL simulator. A good, open source 19 | VHDL simulator is [GHDL](http://ghdl.free.fr/). 20 | -------------------------------------------------------------------------------- /src/rom/Makefile: -------------------------------------------------------------------------------- 1 | # -*- mode: Makefile; tab-width: 8; indent-tabs-mode: t; -*- 2 | #-------------------------------------------------------------------------------------------------- 3 | # Copyright (c) 2019 Marcus Geelnard 4 | # 5 | # This software is provided 'as-is', without any express or implied warranty. In no event will the 6 | # authors be held liable for any damages arising from the use of this software. 7 | # 8 | # Permission is granted to anyone to use this software for any purpose, including commercial 9 | # applications, and to alter it and redistribute it freely, subject to the following restrictions: 10 | # 11 | # 1. The origin of this software must not be misrepresented; you must not claim that you wrote 12 | # the original software. If you use this software in a product, an acknowledgment in the 13 | # product documentation would be appreciated but is not required. 14 | # 15 | # 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 16 | # being the original software. 17 | # 18 | # 3. This notice may not be removed or altered from any source distribution. 19 | #-------------------------------------------------------------------------------------------------- 20 | 21 | OUT = out 22 | SDK_ROOT = ../mc1-sdk 23 | LIBMC1DIR = $(SDK_ROOT)/libmc1 24 | LIBMC1INC = $(LIBMC1DIR)/include 25 | LIBMC1OUT = $(LIBMC1DIR)/out 26 | SELFTESTDIR = ../selftest 27 | SELFTESTOUT = $(SELFTESTDIR)/out 28 | SELFTESTINC = $(SELFTESTDIR)/src 29 | 30 | # TODO(m): Remove -Wno-array-bounds once the GCC 12 bug has been fixed upstream. This is a 31 | # temporary workaround to make the MMIO() macro work (i.e. access a constant address). 32 | # See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101379 33 | CFLAGS_COMMON = -c -I $(LIBMC1INC) -Os -ffast-math \ 34 | -Wall -Wextra -Wshadow -Wno-array-bounds -pedantic -Werror \ 35 | -MMD -MP 36 | 37 | CC = mrisc32-elf-gcc 38 | CCFLAGS = $(CFLAGS_COMMON) -std=c11 39 | CXX = mrisc32-elf-g++ 40 | CXXFLAGS = $(CFLAGS_COMMON) -std=c++17 -Wold-style-cast -fno-exceptions 41 | AS = mrisc32-elf-gcc 42 | ASFLAGS = -c -I $(LIBMC1INC) 43 | LD = mrisc32-elf-gcc 44 | LDFLAGS = -L$(OUT) -L$(SELFTESTOUT) -T link.ld -mno-crt0 -mno-ctor-dtor 45 | AR = mrisc32-elf-ar 46 | ARFLAGS = rcs 47 | OBJCOPY = mrisc32-elf-objcopy 48 | CP = cp -a 49 | 50 | DHRYSTONE_FLAGS = -S -w -fno-inline -O3 51 | 52 | .PHONY: clean all libmc1 selftest 53 | 54 | all: $(OUT)/rom.vhd 55 | 56 | clean: 57 | rm -f $(OUT)/*.a \ 58 | $(OUT)/*.c \ 59 | $(OUT)/*.d \ 60 | $(OUT)/*.s \ 61 | $(OUT)/*.o \ 62 | $(OUT)/*.elf \ 63 | $(OUT)/*.mci \ 64 | $(OUT)/*.raw \ 65 | $(OUT)/*.vhd 66 | $(MAKE) -C $(LIBMC1DIR) clean 67 | $(MAKE) -C $(SELFTESTDIR) clean 68 | 69 | 70 | #----------------------------------------------------------------------------- 71 | # MC1 tools 72 | #----------------------------------------------------------------------------- 73 | 74 | PNG2MCI = $(SDK_ROOT)/tools/png2mci 75 | RAW2C = $(SDK_ROOT)/tools/raw2c.py 76 | 77 | $(PNG2MCI): 78 | @echo "==============================================================================" 79 | @echo " Please build $(PNG2MCI) (see $(SDK_ROOT)/tools/README.md)" 80 | @echo "==============================================================================" 81 | @false 82 | 83 | 84 | #----------------------------------------------------------------------------- 85 | # ROM image 86 | #----------------------------------------------------------------------------- 87 | 88 | # ROM configuration 89 | ENABLE_SPLASH = yes 90 | ENABLE_CONSOLE = no 91 | ENABLE_SELFTEST = no 92 | 93 | ROM_OBJS = \ 94 | $(OUT)/crt0.o \ 95 | $(OUT)/main.o 96 | 97 | ROM_FLAGS = 98 | 99 | ifeq ($(ENABLE_CONSOLE),yes) 100 | ROM_FLAGS += -DENABLE_CONSOLE 101 | ifeq ($(ENABLE_SELFTEST),yes) 102 | ROM_FLAGS += -DENABLE_SELFTEST -I $(SELFTESTINC) 103 | endif 104 | endif 105 | ifeq ($(ENABLE_SPLASH),yes) 106 | ROM_FLAGS += -DENABLE_SPLASH 107 | ROM_OBJS += $(OUT)/boot-splash.o 108 | endif 109 | 110 | $(OUT)/crt0.o: crt0.s $(LIBMC1INC)/mc1/memory.inc $(LIBMC1INC)/mc1/mmio.inc 111 | $(AS) $(ASFLAGS) $(ROM_FLAGS) -o $@ crt0.s 112 | 113 | $(OUT)/main.o: main.cpp 114 | $(CXX) $(CXXFLAGS) $(ROM_FLAGS) -o $@ $< 115 | 116 | $(OUT)/boot-splash.o: media/boot-splash.png 117 | $(PNG2MCI) --lzg --pal4 $< $(OUT)/boot-splash.mci 118 | $(RAW2C) $(OUT)/boot-splash.mci boot_splash_mci > $(OUT)/boot-splash.c 119 | $(CC) $(CCFLAGS) -o $@ $(OUT)/boot-splash.c 120 | 121 | $(OUT)/rom.elf: $(ROM_OBJS) $(OUT)/libmc1.a $(OUT)/libselftest.a link.ld 122 | $(LD) $(LDFLAGS) -o $@ $(ROM_OBJS) -lmc1 -lselftest -lm 123 | 124 | $(OUT)/rom.raw: $(OUT)/rom.elf 125 | $(OBJCOPY) -O binary $< $@ 126 | 127 | $(OUT)/rom.vhd: $(OUT)/rom.raw rom.vhd.in 128 | tools/raw2vhd.py $(OUT)/rom.raw rom.vhd.in > $@ 129 | 130 | 131 | #----------------------------------------------------------------------------- 132 | # libmc1.a 133 | #----------------------------------------------------------------------------- 134 | 135 | # Configure libmc1 to minimize code size (the ROM does not need everything). 136 | LIBMC1_MINI_FLAGS = -Os \ 137 | -DMFAT_ENABLE_WRITE=0 \ 138 | -DMFAT_ENABLE_GPT=0 \ 139 | -DMFAT_ENABLE_OPENDIR=0 140 | 141 | $(OUT)/libmc1.a: libmc1 142 | @$(CP) $(LIBMC1OUT)/libmc1.a $(OUT)/libmc1.a 143 | 144 | libmc1: 145 | $(MAKE) CFLAGS_OPT="$(LIBMC1_MINI_FLAGS)" -C $(LIBMC1DIR) 146 | 147 | 148 | #----------------------------------------------------------------------------- 149 | # libselftest.a - Selftest library 150 | #----------------------------------------------------------------------------- 151 | 152 | $(OUT)/libselftest.a: selftest 153 | @$(CP) $(SELFTESTOUT)/libselftest.a $(OUT)/libselftest.a 154 | 155 | selftest: 156 | $(MAKE) -C $(SELFTESTDIR) 157 | 158 | 159 | # Include dependency files (generated when building the object files). 160 | -include $(ROM_OBJS:.o=.d) 161 | 162 | -------------------------------------------------------------------------------- /src/rom/console.hpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*- 2 | //-------------------------------------------------------------------------------------------------- 3 | // Copyright (c) 2022 Marcus Geelnard 4 | // 5 | // This software is provided 'as-is', without any express or implied warranty. In no event will the 6 | // authors be held liable for any damages arising from the use of this software. 7 | // 8 | // Permission is granted to anyone to use this software for any purpose, including commercial 9 | // applications, and to alter it and redistribute it freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not claim that you wrote 12 | // the original software. If you use this software in a product, an acknowledgment in the 13 | // product documentation would be appreciated but is not required. 14 | // 15 | // 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 16 | // being the original software. 17 | // 18 | // 3. This notice may not be removed or altered from any source distribution. 19 | //-------------------------------------------------------------------------------------------------- 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #ifdef ENABLE_SELFTEST 27 | #include 28 | #endif 29 | 30 | #include 31 | 32 | // Defined by the linker script. 33 | extern char __rom_size; 34 | extern char __bss_start; 35 | extern char __bss_size; 36 | 37 | // Note: Using an anonymous namespace saves a few bytes of code size. 38 | namespace { 39 | 40 | constexpr uint32_t linker_constant(const char* ptr) { 41 | return static_cast(reinterpret_cast(ptr)); 42 | } 43 | 44 | template 45 | constexpr int digit_scalei() { 46 | int scale = 1; 47 | for (int i = 0; i < N; ++i) { 48 | scale *= 10; 49 | } 50 | return scale; 51 | } 52 | 53 | template 54 | constexpr float digit_scalef() { 55 | float scale = 1.0F; 56 | for (int i = 0; i < N; ++i) { 57 | scale *= 10.0F; 58 | } 59 | return scale; 60 | } 61 | 62 | template 63 | void vcon_print_float(const float x) { 64 | auto xi = static_cast(x * digit_scalef()); 65 | constexpr auto iscale = digit_scalei(); 66 | vcon_print_dec(xi / iscale); 67 | if (N > 0) { 68 | auto frac = xi % iscale; 69 | char buf[N + 2]; 70 | buf[0] = '.'; 71 | buf[N + 1] = 0; 72 | for (int i = N; i >= 1; --i) { 73 | buf[i] = '0' + (frac % 10); 74 | frac /= 10; 75 | } 76 | vcon_print(buf); 77 | } 78 | } 79 | 80 | void print_size(uint32_t size) { 81 | static const char* SIZE_SUFFIX[] = {" bytes", " KB", " MB", " GB"}; 82 | int size_div = 0; 83 | while (size >= 1024u && (size & 1023u) == 0u) { 84 | size = size >> 10; 85 | ++size_div; 86 | } 87 | vcon_print_dec(static_cast(size)); 88 | vcon_print(SIZE_SUFFIX[size_div]); 89 | } 90 | 91 | void print_addr_and_size(const char* str, const uint32_t addr, const uint32_t size) { 92 | vcon_print(str); 93 | vcon_print("0x"); 94 | vcon_print_hex(addr); 95 | vcon_print(", "); 96 | print_size(size); 97 | vcon_print("\n"); 98 | } 99 | 100 | // Console class. 101 | class console_t { 102 | public: 103 | void init(void* mem) { 104 | m_vcon_mem = mem; 105 | 106 | // Show the console. 107 | vcon_init(m_vcon_mem); 108 | vcon_set_colors(0, 0xff000000U); 109 | vcon_show(LAYER_2); 110 | 111 | // Print a welcome message. 112 | vcon_print("\n **** MC1 - The MRISC32 computer ****\n\n"); 113 | } 114 | 115 | void deinit() { 116 | vcp_set_prg(LAYER_2, nullptr); 117 | } 118 | 119 | void run_diagnostics() { 120 | // Print some memory information etc. 121 | print_addr_and_size("ROM: ", ROM_START, linker_constant(&__rom_size)); 122 | print_addr_and_size("VRAM: ", VRAM_START, MMIO(VRAMSIZE)); 123 | print_addr_and_size("XRAM: ", XRAM_START, MMIO(XRAMSIZE)); 124 | print_addr_and_size( 125 | "\nbss: ", linker_constant(&__bss_start), linker_constant(&__bss_size)); 126 | 127 | // Print CPU info. 128 | vcon_print("\n\nCPU Freq: "); 129 | vcon_print_float<2>(static_cast(MMIO(CPUCLK)) * (1.0F / 1000000.0F)); 130 | vcon_print(" MHz\n\n"); 131 | 132 | #ifdef ENABLE_SELFTEST 133 | // Run the selftest. 134 | vcon_print("Selftest: "); 135 | if (selftest_run(selftest_callback)) { 136 | vcon_print(" PASS\n\n"); 137 | } else { 138 | vcon_print(" FAIL\n\n"); 139 | } 140 | #endif 141 | 142 | m_diags_have_been_run = true; 143 | } 144 | 145 | bool diags_have_been_run() const { 146 | return m_diags_have_been_run; 147 | } 148 | 149 | static void print(const char* msg) { 150 | vcon_print(msg); 151 | } 152 | 153 | private: 154 | #ifdef ENABLE_SELFTEST 155 | static void selftest_callback(int pass, int /* test_no */) { 156 | vcon_print(pass ? "*" : "!"); 157 | } 158 | #endif 159 | 160 | void* m_vcon_mem; 161 | bool m_diags_have_been_run = false; 162 | }; 163 | 164 | } // namespace 165 | -------------------------------------------------------------------------------- /src/rom/crt0.s: -------------------------------------------------------------------------------- 1 | ; -*- mode: mr32asm; tab-width: 4; indent-tabs-mode: nil; -*- 2 | ; ---------------------------------------------------------------------------- 3 | ; This file contains the common startup code. It defines _start, which does 4 | ; some initialization and then calls main. 5 | ; ---------------------------------------------------------------------------- 6 | 7 | .include "mc1/memory.inc" 8 | .include "mc1/mmio.inc" 9 | 10 | 11 | .macro BOOTSTAGE num:req, sevseg:req 12 | ldi r1, #MMIO_START 13 | ldi r2, #1<<(\num - 1) 14 | stw r2, [r1, #LEDS] 15 | ldi r2, #\sevseg 16 | stw r2, [r1, #SEGDISP0] 17 | .endm 18 | 19 | 20 | .section .text.start, "ax" 21 | 22 | .globl _start 23 | .p2align 2 24 | 25 | _start: 26 | ; ------------------------------------------------------------------------ 27 | ; Clear all CPU registers. 28 | ; ------------------------------------------------------------------------ 29 | 30 | BOOTSTAGE 1, 0b0000110 31 | 32 | ; Set all the scalar registers (except Z, SP and VL) to a known state. 33 | ldi r1, #0 34 | ldi r2, #0 35 | ldi r3, #0 36 | ldi r4, #0 37 | ldi r5, #0 38 | ldi r6, #0 39 | ldi r7, #0 40 | ldi r8, #0 41 | ldi r9, #0 42 | ldi r10, #0 43 | ldi r11, #0 44 | ldi r12, #0 45 | ldi r13, #0 46 | ldi r14, #0 47 | ldi r15, #0 48 | ldi r16, #0 49 | ldi r17, #0 50 | ldi r18, #0 51 | ldi r19, #0 52 | ldi r20, #0 53 | ldi r21, #0 54 | ldi r22, #0 55 | ldi r23, #0 56 | ldi r24, #0 57 | ldi r25, #0 58 | ldi r26, #0 59 | ldi tp, #0 60 | ldi fp, #0 61 | ldi lr, #0 62 | 63 | ; Set all the vector registers to a known state: clear all elements. 64 | ; Also: The default vector length is the max vector register length. 65 | getsr vl, #0x10 66 | or v1, vz, #0 67 | or v2, vz, #0 68 | or v3, vz, #0 69 | or v4, vz, #0 70 | or v5, vz, #0 71 | or v6, vz, #0 72 | or v7, vz, #0 73 | or v8, vz, #0 74 | or v9, vz, #0 75 | or v10, vz, #0 76 | or v11, vz, #0 77 | or v12, vz, #0 78 | or v13, vz, #0 79 | or v14, vz, #0 80 | or v15, vz, #0 81 | or v16, vz, #0 82 | or v17, vz, #0 83 | or v18, vz, #0 84 | or v19, vz, #0 85 | or v20, vz, #0 86 | or v21, vz, #0 87 | or v22, vz, #0 88 | or v23, vz, #0 89 | or v24, vz, #0 90 | or v25, vz, #0 91 | or v26, vz, #0 92 | or v27, vz, #0 93 | or v28, vz, #0 94 | or v29, vz, #0 95 | or v30, vz, #0 96 | or v31, vz, #0 97 | 98 | 99 | ; ------------------------------------------------------------------------ 100 | ; Set up the stack (0.5 KiB at top of VRAM). 101 | ; ------------------------------------------------------------------------ 102 | 103 | ldi r1, #MMIO_START 104 | ldw r1, [r1, #VRAMSIZE] 105 | ldi sp, #VRAM_START 106 | add sp, sp, r1 ; sp = Top of stack (top of VRAM) 107 | 108 | 109 | ; ------------------------------------------------------------------------ 110 | ; Clear the BSS data (if any). 111 | ; ------------------------------------------------------------------------ 112 | 113 | BOOTSTAGE 2, 0b1011011 114 | 115 | ldi r2, #__bss_size 116 | bz r2, bss_cleared 117 | lsr r2, r2, #2 ; BSS size is always a multiple of 4 bytes. 118 | 119 | ldi r1, #__bss_start 120 | getsr vl, #0x10 121 | clear_bss_loop: 122 | minu vl, vl, r2 123 | sub r2, r2, vl 124 | stw vz, [r1, #4] 125 | ldea r1, [r1, vl*4] 126 | bnz r2, clear_bss_loop 127 | bss_cleared: 128 | 129 | 130 | ; ------------------------------------------------------------------------ 131 | ; Make both video layers "silent" (use no memory cycles). 132 | ; We also set the background color for both layers, since the content of 133 | ; the palette registers is undefined after reset. 134 | ; ------------------------------------------------------------------------ 135 | 136 | BOOTSTAGE 3, 0b1001111 137 | 138 | ldi r1, #0x60000000 ; SETPAL 0, 1 139 | ldi r2, #0xff8080a0 ; Color 0 = red tint (ABGR32) 140 | ldi r3, #0x50007fff ; WAITY 32767 = wait forever 141 | ldi r4, #VRAM_START 142 | stw r1, [r4, #16] ; Layer 1 VCP 143 | stw r2, [r4, #20] 144 | stw r3, [r4, #24] 145 | stw r1, [r4, #32] ; Layer 2 VCP 146 | stw z, [r4, #36] ; (fully transparent black for layer 2) 147 | stw r3, [r4, #40] 148 | 149 | 150 | ; ------------------------------------------------------------------------ 151 | ; Call main(). 152 | ; Note: We don't do _init() / _fini() to reduce ROM size, and thus static 153 | ; C++ constructors are not supported in the ROM code. 154 | ; ------------------------------------------------------------------------ 155 | 156 | BOOTSTAGE 4, 0b1100110 157 | 158 | ; r1 = argc, r2 = argv (these are invalid - don't use them!) 159 | ldi r1, #0 160 | ldi r2, #0 161 | 162 | ; Jump to main(). 163 | bl main 164 | 165 | 166 | ; Terminate the program: Loop forever... 167 | BOOTSTAGE 8, 0b1001001 ; 7-segment (three horizontal bars) 168 | 1$: 169 | b 1$ 170 | 171 | -------------------------------------------------------------------------------- /src/rom/fp32.hpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*- 2 | //-------------------------------------------------------------------------------------------------- 3 | // Copyright (c) 2022 Marcus Geelnard 4 | // 5 | // This software is provided 'as-is', without any express or implied warranty. In no event will the 6 | // authors be held liable for any damages arising from the use of this software. 7 | // 8 | // Permission is granted to anyone to use this software for any purpose, including commercial 9 | // applications, and to alter it and redistribute it freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not claim that you wrote 12 | // the original software. If you use this software in a product, an acknowledgment in the 13 | // product documentation would be appreciated but is not required. 14 | // 15 | // 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 16 | // being the original software. 17 | // 18 | // 3. This notice may not be removed or altered from any source distribution. 19 | //-------------------------------------------------------------------------------------------------- 20 | 21 | #ifndef ROM_FP32_HPP_ 22 | #define ROM_FP32_HPP_ 23 | 24 | #include 25 | 26 | // Note: Using an anonymous namespace saves a few bytes of code size. 27 | namespace { 28 | 29 | // A simple fixed-point class for manipulating unsigned values. 30 | // Note: This is mostly to avoid using floating-point instructions in the ROM code. 31 | // 32 | // The internal fixed point format is 12.20 bits, which gives us a valid range of 33 | // 0.000000 - 4095.999999 and 6 decimals precision. This format is suitable for representing 2D 34 | // screen coordinates and sizes. 35 | // 36 | // Usage example: 37 | // 38 | // uint32_t a = 3434U; 39 | // uint32_t b = 12U; 40 | // uint32_t c = static_cast((0.24_fp32 * a) / b); 41 | 42 | class fp32_t { 43 | public: 44 | explicit fp32_t(uint32_t i) : m_fpbits(i << FP_SHIFT) { 45 | } 46 | constexpr explicit fp32_t(long double& d) : m_fpbits(to_fpbits(d)) { 47 | } 48 | 49 | operator uint32_t() const { 50 | // Rounding cast. 51 | return (m_fpbits + (1U << (FP_SHIFT - 1U))) >> FP_SHIFT; 52 | } 53 | 54 | fp32_t& operator+=(const fp32_t y) { 55 | m_fpbits += y.m_fpbits; 56 | return *this; 57 | } 58 | fp32_t& operator*=(const uint32_t y) { 59 | m_fpbits *= y; 60 | return *this; 61 | } 62 | fp32_t& operator/=(const uint32_t y) { 63 | // Rounding division. 64 | m_fpbits = (m_fpbits + (y >> 1U)) / y; 65 | return *this; 66 | } 67 | 68 | private: 69 | static constexpr uint32_t FP_SHIFT = 20U; 70 | 71 | static constexpr uint32_t to_fpbits(long double& d) { 72 | auto i = static_cast(d); 73 | auto f = static_cast((d - static_cast(i)) * 74 | static_cast(1U << FP_SHIFT)); 75 | return (i << FP_SHIFT) | f; 76 | } 77 | 78 | uint32_t m_fpbits; 79 | }; 80 | 81 | constexpr fp32_t operator""_fp32(long double x) { 82 | return fp32_t(x); 83 | } 84 | 85 | fp32_t operator+(fp32_t x, const fp32_t& y) { 86 | x += y; 87 | return x; 88 | } 89 | fp32_t operator*(fp32_t x, const uint32_t& y) { 90 | x *= y; 91 | return x; 92 | } 93 | fp32_t operator/(fp32_t x, const uint32_t& y) { 94 | x /= y; 95 | return x; 96 | } 97 | 98 | } // namespace 99 | 100 | #endif // ROM_FP32_HPP_ 101 | -------------------------------------------------------------------------------- /src/rom/link.ld: -------------------------------------------------------------------------------- 1 | /* -*- mode: ld-script; tab-width: 4; indent-tabs-mode: nil; -*- */ 2 | /* ------------------------------------------------------------------------- */ 3 | /* Linker script for the MC1 ROM. */ 4 | /* ------------------------------------------------------------------------- */ 5 | 6 | OUTPUT_FORMAT("elf32-mrisc32") 7 | OUTPUT_ARCH("mrisc32") 8 | ENTRY(_start) 9 | 10 | __rom_start = 0x00000200; 11 | __vram_start = 0x40000100; /* Leave room for video "registers" */ 12 | 13 | SECTIONS 14 | { 15 | /* --------------------------------------------------------------------- */ 16 | /* Read-only stuff goes into the ROM. */ 17 | /* --------------------------------------------------------------------- */ 18 | 19 | . = __rom_start; 20 | 21 | .text : 22 | { 23 | *(.text.entry) 24 | *(.text.start) 25 | *(.text*) 26 | KEEP (*(SORT_NONE(.init))) 27 | KEEP (*(SORT_NONE(.fini))) 28 | } 29 | . = ALIGN(4); 30 | 31 | .rodata : 32 | { 33 | *(.rodata*) 34 | } 35 | . = ALIGN(4); 36 | 37 | 38 | /* --------------------------------------------------------------------- */ 39 | /* C++ helpers (ctor/dtor/eh_frame) go into the ROM. */ 40 | /* --------------------------------------------------------------------- */ 41 | 42 | .ctor : 43 | { 44 | __CTOR_START = .; 45 | KEEP (*crtbegin.o(.ctors)) 46 | KEEP (*crtbegin?.o(.ctors)) 47 | KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o) .ctors)) 48 | KEEP (*(SORT(.ctors.*))) 49 | KEEP (*(.ctors)) 50 | __CTOR_END = .; 51 | } 52 | . = ALIGN(4); 53 | 54 | .dtor : 55 | { 56 | __DTOR_START = .; 57 | KEEP (*crtbegin.o(.dtors)) 58 | KEEP (*crtbegin?.o(.dtors)) 59 | KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o) .dtors)) 60 | KEEP (*(SORT(.dtors.*))) 61 | KEEP (*(.dtors)) 62 | __DTOR_END = .; 63 | } 64 | . = ALIGN(4); 65 | 66 | .eh_frame : 67 | { 68 | *(.eh_frame*) 69 | } 70 | . = ALIGN(4); 71 | 72 | 73 | /* --------------------------------------------------------------------- */ 74 | /* TODO(m): The .data sections should be r/w. */ 75 | /* For now, we just place them in the ROM. */ 76 | /* --------------------------------------------------------------------- */ 77 | 78 | .data : 79 | { 80 | *(.data*) 81 | } 82 | . = ALIGN(4); 83 | 84 | .sdata : 85 | { 86 | *(.sdata*) 87 | } 88 | 89 | __rom_size = . - 0x00000000; 90 | 91 | 92 | /* --------------------------------------------------------------------- */ 93 | /* BSS goes into VRAM. */ 94 | /* We define __bss_start and __bss_size so the startup code knows what */ 95 | /* memory area to clear. */ 96 | /* --------------------------------------------------------------------- */ 97 | 98 | . = __vram_start; 99 | __bss_start = .; 100 | 101 | .sbss (NOLOAD) : 102 | { 103 | *(.sbss*) 104 | *(.scommon*) 105 | } 106 | . = ALIGN(4); 107 | 108 | .bss (NOLOAD) : 109 | { 110 | *(.bss*) 111 | *(COMMON) 112 | } 113 | . = ALIGN(4); 114 | 115 | __bss_size = . - __bss_start; 116 | 117 | 118 | /* This tells the system where it can start to allocate VRAM. */ 119 | __vram_free_start = .; 120 | } 121 | -------------------------------------------------------------------------------- /src/rom/main.cpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*- 2 | //-------------------------------------------------------------------------------------------------- 3 | // Copyright (c) 2022 Marcus Geelnard 4 | // 5 | // This software is provided 'as-is', without any express or implied warranty. In no event will the 6 | // authors be held liable for any damages arising from the use of this software. 7 | // 8 | // Permission is granted to anyone to use this software for any purpose, including commercial 9 | // applications, and to alter it and redistribute it freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not claim that you wrote 12 | // the original software. If you use this software in a product, an acknowledgment in the 13 | // product documentation would be appreciated but is not required. 14 | // 15 | // 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 16 | // being the original software. 17 | // 18 | // 3. This notice may not be removed or altered from any source distribution. 19 | //-------------------------------------------------------------------------------------------------- 20 | 21 | #include "mosaic.hpp" 22 | 23 | #ifdef ENABLE_SPLASH 24 | #include "splash.hpp" 25 | #endif 26 | #ifdef ENABLE_CONSOLE 27 | #include "console.hpp" 28 | #endif 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | // Defined by the linker script. 38 | extern char __vram_free_start; 39 | 40 | namespace { 41 | // Name of the boot executable file. 42 | const char* BOOT_EXE = "MC1BOOT.EXE"; 43 | 44 | // States for the boot state machine. 45 | enum class boot_state_t { 46 | INITIALIZE, 47 | RUN_DIAGNOSTICS, 48 | WAIT_FOR_SDCARD, 49 | MOUNT_FAT, 50 | LOAD_MC1BOOT, 51 | }; 52 | 53 | // Status of the boot process. 54 | enum class boot_status_t { 55 | NONE, 56 | NO_SDCARD, 57 | NO_FAT, 58 | NO_BOOTEXE, 59 | }; 60 | 61 | // Frame sync class. 62 | class frame_sync_t { 63 | public: 64 | frame_sync_t() : m_t(0) { 65 | m_last_frame_no = MMIO(VIDFRAMENO); 66 | } 67 | 68 | void wait_for_next_frame() { 69 | // Wait for vertical blank. 70 | const auto old_frame_no = MMIO(VIDFRAMENO); 71 | uint32_t frame_no; 72 | do { 73 | frame_no = MMIO(VIDFRAMENO); 74 | } while (frame_no == old_frame_no); 75 | 76 | // Increment T by the number of frames that has passed since the last time we were called. 77 | m_t += frame_no - m_last_frame_no; 78 | m_last_frame_no = frame_no; 79 | } 80 | 81 | uint32_t t() const { 82 | return m_t; 83 | } 84 | 85 | private: 86 | uint32_t m_t; 87 | uint32_t m_last_frame_no; 88 | }; 89 | 90 | // Boot function type. 91 | using boot_fun_t = void(); 92 | 93 | int read_block_fun(char* ptr, unsigned block_no, void* custom) { 94 | auto* ctx = reinterpret_cast(custom); 95 | sdcard_read(ctx, ptr, block_no, 1); 96 | return 0; 97 | } 98 | 99 | int write_block_fun(const char*, unsigned, void*) { 100 | // Not implemented. 101 | return -1; 102 | } 103 | 104 | #ifdef ENABLE_CONSOLE 105 | void sdcard_log_fun(const char* msg) { 106 | console_t::print(msg); 107 | } 108 | #else 109 | #define sdcard_log_fun nullptr 110 | #endif 111 | 112 | } // namespace 113 | 114 | extern "C" int main(int, char**) { 115 | sevseg_print("OLLEH "); // Print a friendly "HELLO". 116 | 117 | mosaic_t mosaic; 118 | #ifdef ENABLE_SPLASH 119 | splash_t splash; 120 | #endif 121 | #ifdef ENABLE_CONSOLE 122 | console_t console; 123 | #endif 124 | sdctx_t sdctx; 125 | frame_sync_t frame_sync; 126 | 127 | auto status = boot_status_t::NONE; 128 | auto previous_status = boot_status_t::NONE; 129 | auto state = boot_state_t::INITIALIZE; 130 | while (true) { 131 | // Update splash screen. 132 | if (state != boot_state_t::INITIALIZE) { 133 | frame_sync.wait_for_next_frame(); 134 | #ifdef ENABLE_SPLASH 135 | splash.update(frame_sync.t()); 136 | #endif 137 | mosaic.update(frame_sync.t()); 138 | 139 | if (status != previous_status) { 140 | #ifdef ENABLE_CONSOLE 141 | const char* msg; 142 | switch (status) { 143 | case boot_status_t::NO_SDCARD: 144 | msg = "Insert bootable SD card\n"; 145 | break; 146 | case boot_status_t::NO_FAT: 147 | msg = "Not a FAT formatted SD card\n"; 148 | break; 149 | case boot_status_t::NO_BOOTEXE: 150 | msg = "No boot executable found\n"; 151 | break; 152 | default: 153 | msg = nullptr; 154 | break; 155 | } 156 | if (msg != nullptr) { 157 | console_t::print(msg); 158 | } 159 | #endif 160 | previous_status = status; 161 | } 162 | } 163 | 164 | // Boot state machine. 165 | switch (state) { 166 | //-------------------------------------------------------------------------------------------- 167 | // INITIALIZE 168 | //-------------------------------------------------------------------------------------------- 169 | default: 170 | case boot_state_t::INITIALIZE: { 171 | auto* mem = reinterpret_cast(&__vram_free_start); 172 | mem = mosaic.init(mem); 173 | #ifdef ENABLE_SPLASH 174 | mem = splash.init(mem); 175 | #endif 176 | #ifdef ENABLE_CONSOLE 177 | console.init(mem); 178 | #endif 179 | state = boot_state_t::RUN_DIAGNOSTICS; 180 | } break; 181 | 182 | //-------------------------------------------------------------------------------------------- 183 | // RUN_DIAGNOSTICS 184 | //-------------------------------------------------------------------------------------------- 185 | case boot_state_t::RUN_DIAGNOSTICS: { 186 | #ifdef ENABLE_CONSOLE 187 | if (!console.diags_have_been_run()) { 188 | console.run_diagnostics(); 189 | } 190 | #endif 191 | state = boot_state_t::WAIT_FOR_SDCARD; 192 | } break; 193 | 194 | //-------------------------------------------------------------------------------------------- 195 | // WAIT_FOR_SDCARD 196 | //-------------------------------------------------------------------------------------------- 197 | case boot_state_t::WAIT_FOR_SDCARD: { 198 | if (sdcard_init(&sdctx, sdcard_log_fun)) { 199 | state = boot_state_t::MOUNT_FAT; 200 | } else { 201 | status = boot_status_t::NO_SDCARD; 202 | } 203 | } break; 204 | 205 | //-------------------------------------------------------------------------------------------- 206 | // MOUNT_FAT 207 | //-------------------------------------------------------------------------------------------- 208 | case boot_state_t::MOUNT_FAT: { 209 | if (mfat_mount(&read_block_fun, &write_block_fun, &sdctx) == 0) { 210 | state = boot_state_t::LOAD_MC1BOOT; 211 | } else { 212 | // Retry the SD card step until we find a valid FAT formatted SD card. 213 | status = boot_status_t::NO_FAT; 214 | state = boot_state_t::WAIT_FOR_SDCARD; 215 | } 216 | } break; 217 | 218 | //-------------------------------------------------------------------------------------------- 219 | // LOAD_MC1BOOT 220 | //-------------------------------------------------------------------------------------------- 221 | case boot_state_t::LOAD_MC1BOOT: { 222 | // Stat the boot exe file to see if it exists. 223 | mfat_stat_t stat; 224 | if (mfat_stat(BOOT_EXE, &stat) == 0) { 225 | // Deinitialize video (blank it while loading the boot executable). 226 | #ifdef ENABLE_CONSOLE 227 | console.deinit(); 228 | #endif 229 | #ifdef ENABLE_SPLASH 230 | splash.deinit(); 231 | #endif 232 | mosaic.deinit(); 233 | 234 | // Try to load the boot executable. 235 | uint32_t entry_address = 0; 236 | if (elf32_load(BOOT_EXE, &entry_address)) { 237 | // Call the boot function. 238 | auto* boot_fun = reinterpret_cast(entry_address); 239 | boot_fun(); 240 | } 241 | 242 | // If we got this far we either could not load the EXE file, or the EXE file has finished 243 | // executing and returned. In either case we can not trust the contents of RAM (e.g. the 244 | // stack), so we need to soft reset. 245 | __asm__ volatile("\tj\tz, #0x00000200"); 246 | } 247 | 248 | // Retry the SD card step until we find a bootable SD card. 249 | status = boot_status_t::NO_BOOTEXE; 250 | state = boot_state_t::WAIT_FOR_SDCARD; 251 | } break; 252 | } 253 | } 254 | 255 | return 0; 256 | } 257 | -------------------------------------------------------------------------------- /src/rom/media/boot-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrisc32/mc1/0ab36c80157da33d715ded4f562b379ff9cfe000/src/rom/media/boot-splash.png -------------------------------------------------------------------------------- /src/rom/media/boot-splash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 48 | 50 | 51 | 53 | image/svg+xml 54 | 56 | 57 | 58 | 59 | 60 | 66 | 73 | 79 | 86 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/rom/mosaic.hpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*- 2 | //-------------------------------------------------------------------------------------------------- 3 | // Copyright (c) 2022 Marcus Geelnard 4 | // 5 | // This software is provided 'as-is', without any express or implied warranty. In no event will the 6 | // authors be held liable for any damages arising from the use of this software. 7 | // 8 | // Permission is granted to anyone to use this software for any purpose, including commercial 9 | // applications, and to alter it and redistribute it freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not claim that you wrote 12 | // the original software. If you use this software in a product, an acknowledgment in the 13 | // product documentation would be appreciated but is not required. 14 | // 15 | // 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 16 | // being the original software. 17 | // 18 | // 3. This notice may not be removed or altered from any source distribution. 19 | //-------------------------------------------------------------------------------------------------- 20 | 21 | #ifndef ROM_MOSAIC_HPP_ 22 | #define ROM_MOSAIC_HPP_ 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #include 30 | 31 | // Note: Using an anonymous namespace saves a few bytes of code size. 32 | namespace { 33 | 34 | // Mosaic background class. 35 | class mosaic_t { 36 | public: 37 | void* init(void* mem) { 38 | // "Allocate" memory. 39 | auto* pixels = reinterpret_cast(mem); 40 | auto* vcp_start = &pixels[MOSAIC_W * MOSAIC_H]; 41 | 42 | // Get the HW resolution. 43 | const auto native_width = MMIO(VIDWIDTH); 44 | const auto native_height = MMIO(VIDHEIGHT); 45 | 46 | // VCP prologue. 47 | auto* vcp = vcp_start; 48 | *vcp++ = vcp_emit_setreg(VCR_XINCR, (0x010000 * MOSAIC_W) / native_width); 49 | *vcp++ = vcp_emit_setreg(VCR_CMODE, CMODE_RGBA8888); 50 | 51 | // Address pointers. 52 | uint32_t vcp_pixels_addr = to_vcp_addr(reinterpret_cast(pixels)); 53 | *vcp++ = vcp_emit_waity(0); 54 | *vcp++ = vcp_emit_setreg(VCR_HSTOP, native_width); 55 | *vcp++ = vcp_emit_setreg(VCR_ADDR, vcp_pixels_addr); 56 | for (int k = 1; k < MOSAIC_H; ++k) { 57 | auto y = (static_cast(k) * native_height) / static_cast(MOSAIC_H); 58 | vcp_pixels_addr += MOSAIC_W; 59 | *vcp++ = vcp_emit_waity(y); 60 | *vcp++ = vcp_emit_setreg(VCR_ADDR, vcp_pixels_addr); 61 | } 62 | 63 | // VCP epilogue: Wait forever. 64 | *vcp++ = vcp_emit_waity(32767); 65 | 66 | // Set up the VCP address. 67 | vcp_set_prg(LAYER_1, vcp_start); 68 | 69 | m_pixels = pixels; 70 | 71 | return reinterpret_cast(vcp); 72 | } 73 | 74 | void deinit() { 75 | vcp_set_prg(LAYER_1, nullptr); 76 | } 77 | 78 | void update(const uint32_t t) { 79 | // Define the four corner colors. 80 | abgr32_t p11 = make_color(t); 81 | abgr32_t p12 = make_color(t + 3433U); 82 | abgr32_t p21 = make_color(1150U - t); 83 | abgr32_t p22 = make_color(t + 13150U); 84 | 85 | // Interpolate all the "pixels" (tiles) in the mosaic. 86 | uint32_t* pixels = m_pixels; 87 | for (int y = 0; y < MOSAIC_H; ++y) { 88 | uint32_t wy = (y << 8) / MOSAIC_H; 89 | abgr32_t p1 = lerp(p11, p21, wy); 90 | abgr32_t p2 = lerp(p12, p22, wy); 91 | for (int x = 0; x < MOSAIC_W; ++x) { 92 | uint32_t wx = (x << 8) / MOSAIC_W; 93 | *pixels++ = lerp(p1, p2, wx); 94 | } 95 | } 96 | } 97 | 98 | private: 99 | // Color type. 100 | using abgr32_t = uint32_t; 101 | 102 | static const int MOSAIC_W = 64; 103 | static const int MOSAIC_H = (MOSAIC_W * 9) / 16; 104 | 105 | static abgr32_t lerp(const abgr32_t c1, const abgr32_t c2, uint32_t w2) { 106 | uint32_t w1 = 255U - w2; 107 | #ifdef __MRISC32_PACKED_OPS__ 108 | uint8x4_t w1p = _mr32_shuf(w1, _MR32_SHUFCTL(0, 0, 0, 0, 0)); // Splat 109 | uint8x4_t w2p = _mr32_shuf(w2, _MR32_SHUFCTL(0, 0, 0, 0, 0)); 110 | return _mr32_mulhiu_b(w1p, c1) + _mr32_mulhiu_b(w2p, c2); 111 | #else 112 | uint32_t br = ((w1 * (c1 & 0xff00ffU) + w2 * (c2 & 0xff00ffU)) >> 8) & 0xff00ffU; 113 | uint32_t g = ((w1 * (c1 & 0x00ff00U) + w2 * (c2 & 0x00ff00U)) >> 8) & 0x00ff00U; 114 | return br | g; 115 | #endif 116 | } 117 | 118 | static uint32_t tri_wave(uint32_t t) { 119 | uint32_t t_mod = t & 511U; 120 | return t_mod <= 255U ? t_mod : 511U - t_mod; 121 | } 122 | 123 | static abgr32_t make_color(uint32_t t) { 124 | uint32_t tr = t; 125 | uint32_t tg = t + 90U; 126 | uint32_t tb = 160U - t; 127 | uint32_t r = tri_wave(tr); 128 | uint32_t g = tri_wave(tg); 129 | uint32_t b = tri_wave(tb); 130 | return r | (g << 8) | (b << 16); 131 | } 132 | 133 | uint32_t* m_pixels; 134 | }; 135 | 136 | } // namespace 137 | 138 | #endif // ROM_MOSAIC_HPP_ 139 | -------------------------------------------------------------------------------- /src/rom/out/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /src/rom/rom.vhd.in: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a single-ported ROM (Wishbone B4 pipelined interface). 22 | ---------------------------------------------------------------------------------------------------- 23 | 24 | library ieee; 25 | use ieee.std_logic_1164.all; 26 | use ieee.numeric_std.all; 27 | 28 | entity rom is 29 | port( 30 | -- Control signals. 31 | i_clk : in std_logic; 32 | 33 | -- Wishbone memory interface (b4 pipelined slave). 34 | -- See: https://cdn.opencores.org/downloads/wbspec_b4.pdf 35 | i_wb_cyc : in std_logic; 36 | i_wb_stb : in std_logic; 37 | i_wb_adr : in std_logic_vector(29 downto 0); 38 | o_wb_dat : out std_logic_vector(31 downto 0); 39 | o_wb_ack : out std_logic; 40 | o_wb_stall : out std_logic 41 | ); 42 | end rom; 43 | 44 | architecture rtl of rom is 45 | constant C_ADDR_BITS : positive := ${ADDR_BITS}; 46 | subtype WORD_T is std_logic_vector(31 downto 0); 47 | type MEM_T is array (0 to 2**C_ADDR_BITS-1) of WORD_T; 48 | signal C_ROM : MEM_T := ( 49 | ${DATA} 50 | ); 51 | 52 | signal s_is_valid_wb_request : std_logic; 53 | signal s_rom_addr : unsigned(C_ADDR_BITS-1 downto 0); 54 | signal s_dat : WORD_T := (others => '0'); 55 | begin 56 | -- Wishbone control logic. We always ack and never stall - we're that fast ;-) 57 | s_is_valid_wb_request <= i_wb_cyc and i_wb_stb; 58 | process(i_clk) 59 | begin 60 | if rising_edge(i_clk) then 61 | o_wb_ack <= s_is_valid_wb_request; 62 | end if; 63 | end process; 64 | o_wb_stall <= '0'; 65 | 66 | -- Actual ROM. 67 | s_rom_addr <= unsigned(i_wb_adr(C_ADDR_BITS-1 downto 0)); 68 | process(i_clk) 69 | begin 70 | if rising_edge(i_clk) then 71 | s_dat <= C_ROM(to_integer(s_rom_addr)); 72 | end if; 73 | end process; 74 | 75 | -- Output signal. 76 | o_wb_dat <= s_dat; 77 | end rtl; 78 | -------------------------------------------------------------------------------- /src/rom/splash.hpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*- 2 | //-------------------------------------------------------------------------------------------------- 3 | // Copyright (c) 2022 Marcus Geelnard 4 | // 5 | // This software is provided 'as-is', without any express or implied warranty. In no event will the 6 | // authors be held liable for any damages arising from the use of this software. 7 | // 8 | // Permission is granted to anyone to use this software for any purpose, including commercial 9 | // applications, and to alter it and redistribute it freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not claim that you wrote 12 | // the original software. If you use this software in a product, an acknowledgment in the 13 | // product documentation would be appreciated but is not required. 14 | // 15 | // 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 16 | // being the original software. 17 | // 18 | // 3. This notice may not be removed or altered from any source distribution. 19 | //-------------------------------------------------------------------------------------------------- 20 | 21 | #ifndef ROM_SPLASH_HPP_ 22 | #define ROM_SPLASH_HPP_ 23 | 24 | #include "fp32.hpp" 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | // The boot splash image is linked in from a separate file. 33 | extern const unsigned char boot_splash_mci[] __attribute__((aligned(4))); 34 | 35 | // Note: Using an anonymous namespace saves a few bytes of code size. 36 | namespace { 37 | 38 | // Splash display class. 39 | class splash_t { 40 | public: 41 | void* init(void* mem) { 42 | // Decode the MCI header. 43 | auto* hdr = mci_get_header(boot_splash_mci); 44 | const auto pixels_size = mci_get_pixels_size(hdr); 45 | m_num_palette_colors = hdr->num_pal_colors; 46 | m_img_width = hdr->width; 47 | m_img_height = hdr->height; 48 | m_img_fmt = hdr->pixel_format; 49 | m_img_word_stride = mci_get_stride(hdr) / 4; 50 | 51 | // "Allocate" memory. 52 | m_pixels = reinterpret_cast(mem); 53 | m_vcp = reinterpret_cast(reinterpret_cast(mem) + pixels_size); 54 | 55 | // Decode the pixels. 56 | mci_decode_pixels(boot_splash_mci, m_pixels); 57 | 58 | // Generate the VCP. 59 | auto* mem_end = generate_vcp(scale_for_t(0)); 60 | 61 | // Set up the VCP address. 62 | vcp_set_prg(LAYER_2, m_vcp); 63 | 64 | return mem_end; 65 | } 66 | 67 | void deinit() { 68 | vcp_set_prg(LAYER_2, nullptr); 69 | } 70 | 71 | void update(const uint32_t t) { 72 | (void)generate_vcp(scale_for_t(t)); 73 | } 74 | 75 | private: 76 | static fp32_t scale_for_t(const uint32_t t) { 77 | // Scaling as a function of time: Simulate an x^2 "bouncing" motion. 78 | auto t_mod = t & 127U; 79 | if (t_mod >= 64U) { 80 | t_mod = 127U - t_mod; 81 | } 82 | return 0.75_fp32 + 0.000126_fp32 * ((63U * 63U) - (t_mod * t_mod)); 83 | } 84 | 85 | void* generate_vcp(fp32_t scale_for_1080p) { 86 | // Get the HW resolution and adjust the scaling factor. 87 | const auto native_width = MMIO(VIDWIDTH); 88 | const auto native_height = MMIO(VIDHEIGHT); 89 | auto scale = (scale_for_1080p * native_height) / static_cast(1080); 90 | 91 | // Calculate the screen rectangle for the splash (centered, preserve aspect ratio). 92 | const auto view_height = static_cast(scale * m_img_height); 93 | const auto view_width = static_cast(scale * m_img_width); 94 | const auto view_top = (native_height - view_height) / 2U; 95 | const auto view_left = (native_width - view_width) / 2U; 96 | 97 | auto* vcp = m_vcp; 98 | 99 | // We add a wait here, and add a few NOP:s (to fill up the pipeline after the WAITY instruction) 100 | // so that the VCP modifications that we do during the blanking interval has effect. 101 | *vcp++ = vcp_emit_waity(0); 102 | *vcp++ = vcp_emit_nop(); 103 | *vcp++ = vcp_emit_nop(); 104 | *vcp++ = vcp_emit_nop(); 105 | 106 | // VCP prologue. 107 | // TODO(m): Use the fixed point width from the scaling and set VCR_XOFFS too for subpixel 108 | // accuracy. 109 | *vcp++ = vcp_emit_setreg(VCR_XINCR, (0x010000U * m_img_width) / view_width); 110 | *vcp++ = vcp_emit_setreg(VCR_CMODE, m_img_fmt); 111 | 112 | // Palette. 113 | *vcp++ = vcp_emit_setpal(0, m_num_palette_colors); 114 | m_palette = vcp; 115 | mci_decode_palette(boot_splash_mci, vcp); 116 | vcp += m_num_palette_colors; 117 | 118 | // Address pointers. 119 | *vcp++ = vcp_emit_waity(view_top); 120 | *vcp++ = vcp_emit_setreg(VCR_HSTRT, view_left); 121 | *vcp++ = vcp_emit_setreg(VCR_HSTOP, view_left + view_width); 122 | uint32_t vcp_pixels_addr = to_vcp_addr(reinterpret_cast(m_pixels)); 123 | const auto vcp_pixels_stride = m_img_word_stride; 124 | auto y = fp32_t(view_top); 125 | const auto y_step = fp32_t(view_height) / m_img_height; 126 | for (uint32_t k = 0U; k < m_img_height; ++k) { 127 | *vcp++ = vcp_emit_waity(static_cast(y)); 128 | *vcp++ = vcp_emit_setreg(VCR_ADDR, vcp_pixels_addr); 129 | y += y_step; 130 | vcp_pixels_addr += vcp_pixels_stride; 131 | } 132 | *vcp++ = vcp_emit_waity(static_cast(y)); 133 | *vcp++ = vcp_emit_setreg(VCR_HSTOP, 0); 134 | 135 | // VCP epilogue: Wait forever. 136 | *vcp++ = vcp_emit_waity(32767); 137 | 138 | return reinterpret_cast(vcp); 139 | } 140 | 141 | uint32_t* m_pixels; 142 | uint32_t* m_vcp; 143 | uint32_t* m_palette; 144 | uint32_t m_num_palette_colors; 145 | uint32_t m_img_width; 146 | uint32_t m_img_height; 147 | uint32_t m_img_fmt; 148 | uint32_t m_img_word_stride; 149 | }; 150 | 151 | } // namespace 152 | 153 | #endif // ROM_SPLASH_HPP_ 154 | -------------------------------------------------------------------------------- /src/rom/tools/raw2vhd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- mode: python; tab-width: 4; indent-tabs-mode: nil; -*- 3 | 4 | import argparse 5 | import math 6 | import struct 7 | 8 | _RAW_BASE_ADDRESS = 512 9 | 10 | 11 | def closest_pot(x): 12 | res = 2 13 | while x > res: 14 | res = res * 2 15 | return res 16 | 17 | 18 | def convert(raw_filename, template_filename): 19 | # Read the raw rom file and pad start and end with zeros to account for start address and 20 | # a power-of-two size. 21 | with open(raw_filename, 'rb') as f: 22 | raw_data = f.read() 23 | raw_data = bytearray(_RAW_BASE_ADDRESS) + raw_data 24 | rom_size = int(closest_pot(len(raw_data))) 25 | raw_data = raw_data + bytearray(rom_size - len(raw_data)) 26 | 27 | # Derive dynamic data. 28 | ADDR_BITS = str(int(math.log(rom_size, 2) - 2)) 29 | DATA = '' 30 | raw_data_32bit = struct.unpack('<' + ('I' * (rom_size // 4)), raw_data) 31 | for x in range(0, len(raw_data_32bit)): 32 | tail = '' if x == (len(raw_data_32bit) - 1) else ',\n' 33 | word = raw_data_32bit[x] 34 | DATA = DATA + (f' x"{word:08x}"{tail}') 35 | 36 | # Read the VHDL template. 37 | with open(template_filename, 'r', encoding='utf8') as f: 38 | template = f.readlines() 39 | 40 | # Generate the output. 41 | for l in template: 42 | l = l.rstrip() 43 | l = l.replace("${ADDR_BITS}", ADDR_BITS) 44 | l = l.replace("${DATA}", DATA) 45 | print(l) 46 | 47 | 48 | def main(): 49 | # Parse command line arguments. 50 | parser = argparse.ArgumentParser( 51 | description='Convert a raw file to a VHDL ROM file') 52 | parser.add_argument('raw', metavar='RAW_FILE', help='the raw file to convert') 53 | parser.add_argument('template', metavar='TEMPLATE_FILE', help='the VHDL template file') 54 | args = parser.parse_args() 55 | 56 | # Convert the file. 57 | convert(args.raw, args.template) 58 | 59 | 60 | if __name__ == "__main__": 61 | main() 62 | -------------------------------------------------------------------------------- /src/rtl/bit_synchronizer.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2020 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a two-flip-flop synchronization circuit for single-bit signals. 22 | -- 23 | -- In addition to passing a signal over from one clock domain to another, this design also employs 24 | -- mitigations bounces and instabilities. This is done by detecting changes in the signal and only 25 | -- propagating the new signal value to the output once the signal has stayed constant for a certain 26 | -- number of clock cycles (this functionality is optional). 27 | ---------------------------------------------------------------------------------------------------- 28 | 29 | library ieee; 30 | use ieee.std_logic_1164.all; 31 | use ieee.numeric_std.all; 32 | 33 | entity bit_synchronizer is 34 | generic( 35 | STEADY_CYCLES : integer := 3 36 | ); 37 | port( 38 | i_rst : in std_logic; 39 | 40 | -- Clock signal for the target clock domain. 41 | i_clk : in std_logic; 42 | 43 | -- Signal from the source clock domain (or an asynchronous signal). 44 | i_d : in std_logic; 45 | 46 | -- Synchronized signal. 47 | o_q : out std_logic 48 | ); 49 | end bit_synchronizer; 50 | 51 | architecture rtl of bit_synchronizer is 52 | -- Signals for the synchronizer flip-flops. 53 | signal s_metastable : std_logic; 54 | signal s_stable : std_logic; 55 | 56 | -- Signals for the value change detector. 57 | signal s_prev_stable : std_logic; 58 | signal s_stable_changed : std_logic; 59 | signal s_steady_cycles : integer range 0 to STEADY_CYCLES; 60 | 61 | -- Intel/Altera specific constraints. 62 | attribute ALTERA_ATTRIBUTE : string; 63 | attribute ALTERA_ATTRIBUTE of rtl : architecture is "-name SDC_STATEMENT ""set_false_path -to [get_registers {*|bit_synchronizer:*|s_metastable*}] """; 64 | attribute ALTERA_ATTRIBUTE of s_metastable : signal is "-name SYNCHRONIZER_IDENTIFICATION ""FORCED IF ASYNCHRONOUS"""; 65 | attribute PRESERVE : boolean; 66 | attribute PRESERVE of s_metastable : signal is true; 67 | attribute PRESERVE of s_stable : signal is true; 68 | 69 | -- Xilinx specific constraints. 70 | attribute ASYNC_REG : string; 71 | attribute ASYNC_REG of s_metastable : signal is "TRUE"; 72 | attribute SHREG_EXTRACT : string; 73 | attribute SHREG_EXTRACT of s_metastable : signal is "NO"; 74 | attribute SHREG_EXTRACT of s_stable : signal is "NO"; 75 | begin 76 | -- Synchronize the source signal using two flip-flops in series. 77 | process(i_rst, i_clk) 78 | begin 79 | if i_rst = '1' then 80 | s_metastable <= '0'; 81 | s_stable <= '0'; 82 | elsif rising_edge(i_clk) then 83 | s_metastable <= i_d; 84 | s_stable <= s_metastable; 85 | end if; 86 | end process; 87 | 88 | SteadyGen: if STEADY_CYCLES > 0 generate 89 | -- Only accept a value after STEADY_CYCLES cycles of steady state. 90 | s_stable_changed <= '1' when s_stable /= s_prev_stable else '0'; 91 | 92 | process(i_rst, i_clk) 93 | begin 94 | if i_rst = '1' then 95 | s_prev_stable <= '0'; 96 | s_steady_cycles <= 0; 97 | o_q <= '0'; 98 | elsif rising_edge(i_clk) then 99 | -- Count the number of steady cycles that we have. 100 | if s_stable_changed = '1' then 101 | s_steady_cycles <= 0; 102 | else 103 | s_steady_cycles <= s_steady_cycles + 1; 104 | end if; 105 | 106 | -- Time to update the output value? 107 | if s_steady_cycles = STEADY_CYCLES then 108 | o_q <= s_stable; 109 | end if; 110 | 111 | s_prev_stable <= s_stable; 112 | end if; 113 | end process; 114 | else generate 115 | o_q <= s_stable; 116 | end generate; 117 | end rtl; 118 | -------------------------------------------------------------------------------- /src/rtl/de0_cv/constraints.sdc: -------------------------------------------------------------------------------- 1 | # Constrain the input clock ports with a 20 ns requirement (50 MHz). 2 | create_clock -period 20 [get_ports CLOCK_50] 3 | create_clock -period 20 [get_ports CLOCK2_50] 4 | create_clock -period 20 [get_ports CLOCK3_50] 5 | create_clock -period 20 [get_ports CLOCK4_50] 6 | 7 | # Constrain the GPIO-0 pin 1 as an input clock port with a 20 ns requirement (50 MHz). 8 | create_clock -period 20 [get_ports GPIO_0[0]] 9 | 10 | # Automatically apply a generate clock on the output of phase-locked loops (PLLs). 11 | # This command can be safely left in the SDC even if no PLLs exist in the design. 12 | derive_pll_clocks 13 | 14 | # The PLL:s generate three main clocks. 15 | set cpu_pll "pll_cpu|pll_1|altera_pll_i|general[0].gpll~PLL_OUTPUT_COUNTER|divclk" 16 | set sdram_pll "pll_cpu|pll_1|altera_pll_i|general[1].gpll~PLL_OUTPUT_COUNTER|divclk" 17 | set vga_pll "pll_vga|pll_1|altera_pll_i|general[0].gpll~PLL_OUTPUT_COUNTER|divclk" 18 | 19 | # SDRAM clock. 20 | create_generated_clock -name sdram_clk -source $sdram_pll [get_ports {DRAM_CLK}] 21 | 22 | # Set Clock Uncertainty 23 | derive_clock_uncertainty 24 | 25 | # SDRAM timing. 26 | set sdram_tsu 1.5 27 | set sdram_th 0.8 28 | set sdram_tco_min 2.7 29 | set sdram_tco_max 5.4 30 | 31 | # SDRAM timing constraints. 32 | set sdram_input_delay_min $sdram_tco_min 33 | set sdram_input_delay_max $sdram_tco_max 34 | set sdram_output_delay_min -$sdram_th 35 | set sdram_output_delay_max $sdram_tsu 36 | 37 | # PLL to SDRAM output (clear the unconstrained path warning). 38 | set_min_delay -from $sdram_pll -to [get_ports {DRAM_CLK}] 1 39 | set_max_delay -from $sdram_pll -to [get_ports {DRAM_CLK}] 6 40 | 41 | # SDRAM outputs. 42 | set sdram_outputs [get_ports { 43 | DRAM_CKE 44 | DRAM_ADDR[*] 45 | DRAM_BA[*] 46 | DRAM_DQ[*] 47 | DRAM_CS_N 48 | DRAM_RAS_N 49 | DRAM_CAS_N 50 | DRAM_WE_N 51 | DRAM_LDQM 52 | DRAM_UDQM 53 | }] 54 | set_output_delay \ 55 | -clock sdram_clk \ 56 | -min $sdram_output_delay_min \ 57 | $sdram_outputs 58 | set_output_delay \ 59 | -clock sdram_clk \ 60 | -max $sdram_output_delay_max \ 61 | $sdram_outputs 62 | 63 | # SDRAM inputs. 64 | set sdram_inputs [get_ports { 65 | DRAM_DQ[*] 66 | }] 67 | set_input_delay \ 68 | -clock sdram_clk \ 69 | -min $sdram_input_delay_min \ 70 | $sdram_inputs 71 | set_input_delay \ 72 | -clock sdram_clk \ 73 | -max $sdram_input_delay_max \ 74 | $sdram_inputs 75 | 76 | # SDRAM-to-FPGA multi-cycle constraint. 77 | # 78 | # * The PLL is configured so that SDRAM clock leads the CPU clock. 79 | set_multicycle_path -setup -end -from sdram_clk -to $cpu_pll 2 80 | 81 | -------------------------------------------------------------------------------- /src/rtl/de0_cv/pll.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a VHDL wrapper around the Verilog version of the Intel PLL. 22 | ---------------------------------------------------------------------------------------------------- 23 | 24 | library ieee; 25 | use ieee.std_logic_1164.all; 26 | 27 | entity pll is 28 | generic( 29 | REFERENCE_CLOCK_FREQUENCY : integer := 100_000_000; 30 | NUMBER_OF_CLOCKS : positive := 1; 31 | 32 | OUTPUT_CLOCK_FREQUENCY0 : integer := 100_000_000; 33 | PHASE_SHIFT0 : time := 0 ps; 34 | DUTY_CYCLE0 : positive := 50; 35 | 36 | OUTPUT_CLOCK_FREQUENCY1 : integer := 0; 37 | PHASE_SHIFT1 : time := 0 ps; 38 | DUTY_CYCLE1 : positive := 50; 39 | 40 | OUTPUT_CLOCK_FREQUENCY2 : integer := 0; 41 | PHASE_SHIFT2 : time := 0 ps; 42 | DUTY_CYCLE2 : positive := 50; 43 | 44 | OUTPUT_CLOCK_FREQUENCY3 : integer := 0; 45 | PHASE_SHIFT3 : time := 0 ps; 46 | DUTY_CYCLE3 : positive := 50; 47 | 48 | OUTPUT_CLOCK_FREQUENCY4 : integer := 0; 49 | PHASE_SHIFT4 : time := 0 ps; 50 | DUTY_CYCLE4 : positive := 50; 51 | 52 | OUTPUT_CLOCK_FREQUENCY5 : integer := 0; 53 | PHASE_SHIFT5 : time := 0 ps; 54 | DUTY_CYCLE5 : positive := 50; 55 | 56 | OUTPUT_CLOCK_FREQUENCY6 : integer := 0; 57 | PHASE_SHIFT6 : time := 0 ps; 58 | DUTY_CYCLE6 : positive := 50; 59 | 60 | OUTPUT_CLOCK_FREQUENCY7 : integer := 0; 61 | PHASE_SHIFT7 : time := 0 ps; 62 | DUTY_CYCLE7 : positive := 50 63 | ); 64 | port( 65 | i_rst : in std_logic; 66 | i_refclk : in std_logic; 67 | 68 | o_locked : out std_logic; 69 | o_clk0 : out std_logic; 70 | o_clk1 : out std_logic; 71 | o_clk2 : out std_logic; 72 | o_clk3 : out std_logic; 73 | o_clk4 : out std_logic; 74 | o_clk5 : out std_logic; 75 | o_clk6 : out std_logic; 76 | o_clk7 : out std_logic 77 | ); 78 | end pll; 79 | 80 | 81 | architecture rtl of pll is 82 | component pll_intel is 83 | generic( 84 | REFERENCE_CLOCK_FREQUENCY : string; 85 | NUMBER_OF_CLOCKS : positive; 86 | 87 | OUTPUT_CLOCK_FREQUENCY0 : string; 88 | PHASE_SHIFT0 : string; 89 | DUTY_CYCLE0 : positive; 90 | 91 | OUTPUT_CLOCK_FREQUENCY1 : string; 92 | PHASE_SHIFT1 : string; 93 | DUTY_CYCLE1 : positive; 94 | 95 | OUTPUT_CLOCK_FREQUENCY2 : string; 96 | PHASE_SHIFT2 : string; 97 | DUTY_CYCLE2 : positive; 98 | 99 | OUTPUT_CLOCK_FREQUENCY3 : string; 100 | PHASE_SHIFT3 : string; 101 | DUTY_CYCLE3 : positive; 102 | 103 | OUTPUT_CLOCK_FREQUENCY4 : string; 104 | PHASE_SHIFT4 : string; 105 | DUTY_CYCLE4 : positive; 106 | 107 | OUTPUT_CLOCK_FREQUENCY5 : string; 108 | PHASE_SHIFT5 : string; 109 | DUTY_CYCLE5 : positive; 110 | 111 | OUTPUT_CLOCK_FREQUENCY6 : string; 112 | PHASE_SHIFT6 : string; 113 | DUTY_CYCLE6 : positive; 114 | 115 | OUTPUT_CLOCK_FREQUENCY7 : string; 116 | PHASE_SHIFT7 : string; 117 | DUTY_CYCLE7 : positive 118 | ); 119 | port( 120 | i_rst : in std_logic; 121 | i_refclk : in std_logic; 122 | 123 | o_locked : out std_logic; 124 | o_clk0 : out std_logic; 125 | o_clk1 : out std_logic; 126 | o_clk2 : out std_logic; 127 | o_clk3 : out std_logic; 128 | o_clk4 : out std_logic; 129 | o_clk5 : out std_logic; 130 | o_clk6 : out std_logic; 131 | o_clk7 : out std_logic 132 | ); 133 | end component; 134 | 135 | function to_mhz_string(hz : integer) return string is 136 | variable v_mhz : real; 137 | begin 138 | v_mhz := real(hz) / 1000000.0; 139 | return real'image(v_mhz) & " MHz"; 140 | end function; 141 | 142 | function to_string(t : time) return string is 143 | begin 144 | if t = 0 ps then 145 | return "0 ps"; 146 | end if; 147 | return real'image(real(t / 1 ps)) & " ps"; 148 | end function; 149 | begin 150 | pll_1: pll_intel 151 | generic map ( 152 | REFERENCE_CLOCK_FREQUENCY => to_mhz_string(REFERENCE_CLOCK_FREQUENCY), 153 | NUMBER_OF_CLOCKS => NUMBER_OF_CLOCKS, 154 | 155 | OUTPUT_CLOCK_FREQUENCY0 => to_mhz_string(OUTPUT_CLOCK_FREQUENCY0), 156 | PHASE_SHIFT0 => to_string(PHASE_SHIFT0), 157 | DUTY_CYCLE0 => DUTY_CYCLE0, 158 | 159 | OUTPUT_CLOCK_FREQUENCY1 => to_mhz_string(OUTPUT_CLOCK_FREQUENCY1), 160 | PHASE_SHIFT1 => to_string(PHASE_SHIFT1), 161 | DUTY_CYCLE1 => DUTY_CYCLE1, 162 | 163 | OUTPUT_CLOCK_FREQUENCY2 => to_mhz_string(OUTPUT_CLOCK_FREQUENCY2), 164 | PHASE_SHIFT2 => to_string(PHASE_SHIFT2), 165 | DUTY_CYCLE2 => DUTY_CYCLE2, 166 | 167 | OUTPUT_CLOCK_FREQUENCY3 => to_mhz_string(OUTPUT_CLOCK_FREQUENCY3), 168 | PHASE_SHIFT3 => to_string(PHASE_SHIFT3), 169 | DUTY_CYCLE3 => DUTY_CYCLE3, 170 | 171 | OUTPUT_CLOCK_FREQUENCY4 => to_mhz_string(OUTPUT_CLOCK_FREQUENCY4), 172 | PHASE_SHIFT4 => to_string(PHASE_SHIFT4), 173 | DUTY_CYCLE4 => DUTY_CYCLE4, 174 | 175 | OUTPUT_CLOCK_FREQUENCY5 => to_mhz_string(OUTPUT_CLOCK_FREQUENCY5), 176 | PHASE_SHIFT5 => to_string(PHASE_SHIFT5), 177 | DUTY_CYCLE5 => DUTY_CYCLE5, 178 | 179 | OUTPUT_CLOCK_FREQUENCY6 => to_mhz_string(OUTPUT_CLOCK_FREQUENCY6), 180 | PHASE_SHIFT6 => to_string(PHASE_SHIFT6), 181 | DUTY_CYCLE6 => DUTY_CYCLE6, 182 | 183 | OUTPUT_CLOCK_FREQUENCY7 => to_mhz_string(OUTPUT_CLOCK_FREQUENCY7), 184 | PHASE_SHIFT7 => to_string(PHASE_SHIFT7), 185 | DUTY_CYCLE7 => DUTY_CYCLE7 186 | ) 187 | port map ( 188 | i_rst => i_rst, 189 | i_refclk => i_refclk, 190 | 191 | o_locked => o_locked, 192 | o_clk0 => o_clk0, 193 | o_clk1 => o_clk1, 194 | o_clk2 => o_clk2, 195 | o_clk3 => o_clk3, 196 | o_clk4 => o_clk4, 197 | o_clk5 => o_clk5, 198 | o_clk6 => o_clk6, 199 | o_clk7 => o_clk7 200 | ); 201 | end rtl; 202 | -------------------------------------------------------------------------------- /src/rtl/de0_cv/pll_intel.v: -------------------------------------------------------------------------------- 1 | //-------------------------------------------------------------------------------------------------- 2 | // Copyright (c) 2019 Marcus Geelnard 3 | // 4 | // This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | // authors be held liable for any damages arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, including commercial 8 | // applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | // 10 | // 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | // the original software. If you use this software in a product, an acknowledgment in the 12 | // product documentation would be appreciated but is not required. 13 | // 14 | // 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | // being the original software. 16 | // 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | //-------------------------------------------------------------------------------------------------- 19 | 20 | //-------------------------------------------------------------------------------------------------- 21 | // This is a simple parameterized Intel flavor PLL. It infers an "altera_pll" IP that exposes up to 22 | // eight output clocks (o_clk0 to o_clk7). 23 | //-------------------------------------------------------------------------------------------------- 24 | 25 | `timescale 1ns/10ps 26 | module pll_intel( 27 | input wire i_rst, 28 | input wire i_refclk, 29 | output wire o_locked, 30 | output wire o_clk0, 31 | output wire o_clk1, 32 | output wire o_clk2, 33 | output wire o_clk3, 34 | output wire o_clk4, 35 | output wire o_clk5, 36 | output wire o_clk6, 37 | output wire o_clk7 38 | ); 39 | 40 | parameter REFERENCE_CLOCK_FREQUENCY = "100.0 MHz"; 41 | parameter NUMBER_OF_CLOCKS = 1; 42 | parameter OUTPUT_CLOCK_FREQUENCY0 = "100.0 MHz"; 43 | parameter PHASE_SHIFT0 = "0 ps"; 44 | parameter DUTY_CYCLE0 = 50; 45 | parameter OUTPUT_CLOCK_FREQUENCY1 = "0 MHz"; 46 | parameter PHASE_SHIFT1 = "0 ps"; 47 | parameter DUTY_CYCLE1 = 50; 48 | parameter OUTPUT_CLOCK_FREQUENCY2 = "0 MHz"; 49 | parameter PHASE_SHIFT2 = "0 ps"; 50 | parameter DUTY_CYCLE2 = 50; 51 | parameter OUTPUT_CLOCK_FREQUENCY3 = "0 MHz"; 52 | parameter PHASE_SHIFT3 = "0 ps"; 53 | parameter DUTY_CYCLE3 = 50; 54 | parameter OUTPUT_CLOCK_FREQUENCY4 = "0 MHz"; 55 | parameter PHASE_SHIFT4 = "0 ps"; 56 | parameter DUTY_CYCLE4 = 50; 57 | parameter OUTPUT_CLOCK_FREQUENCY5 = "0 MHz"; 58 | parameter PHASE_SHIFT5 = "0 ps"; 59 | parameter DUTY_CYCLE5 = 50; 60 | parameter OUTPUT_CLOCK_FREQUENCY6 = "0 MHz"; 61 | parameter PHASE_SHIFT6 = "0 ps"; 62 | parameter DUTY_CYCLE6 = 50; 63 | parameter OUTPUT_CLOCK_FREQUENCY7 = "0 MHz"; 64 | parameter PHASE_SHIFT7 = "0 ps"; 65 | parameter DUTY_CYCLE7 = 50; 66 | 67 | altera_pll #( 68 | .fractional_vco_multiplier("false"), 69 | .reference_clock_frequency(REFERENCE_CLOCK_FREQUENCY), 70 | .operation_mode("direct"), 71 | .number_of_clocks(NUMBER_OF_CLOCKS), 72 | .output_clock_frequency0(OUTPUT_CLOCK_FREQUENCY0), 73 | .phase_shift0(PHASE_SHIFT0), 74 | .duty_cycle0(DUTY_CYCLE0), 75 | .output_clock_frequency1(OUTPUT_CLOCK_FREQUENCY1), 76 | .phase_shift1(PHASE_SHIFT1), 77 | .duty_cycle1(DUTY_CYCLE1), 78 | .output_clock_frequency2(OUTPUT_CLOCK_FREQUENCY2), 79 | .phase_shift2(PHASE_SHIFT2), 80 | .duty_cycle2(DUTY_CYCLE2), 81 | .output_clock_frequency3(OUTPUT_CLOCK_FREQUENCY3), 82 | .phase_shift3(PHASE_SHIFT3), 83 | .duty_cycle3(DUTY_CYCLE3), 84 | .output_clock_frequency4(OUTPUT_CLOCK_FREQUENCY4), 85 | .phase_shift4(PHASE_SHIFT4), 86 | .duty_cycle4(DUTY_CYCLE4), 87 | .output_clock_frequency5(OUTPUT_CLOCK_FREQUENCY5), 88 | .phase_shift5(PHASE_SHIFT5), 89 | .duty_cycle5(DUTY_CYCLE5), 90 | .output_clock_frequency6(OUTPUT_CLOCK_FREQUENCY6), 91 | .phase_shift6(PHASE_SHIFT6), 92 | .duty_cycle6(DUTY_CYCLE6), 93 | .output_clock_frequency7(OUTPUT_CLOCK_FREQUENCY7), 94 | .phase_shift7(PHASE_SHIFT7), 95 | .duty_cycle7(DUTY_CYCLE7), 96 | .output_clock_frequency8("0 MHz"), 97 | .phase_shift8("0 ps"), 98 | .duty_cycle8(50), 99 | .output_clock_frequency9("0 MHz"), 100 | .phase_shift9("0 ps"), 101 | .duty_cycle9(50), 102 | .output_clock_frequency10("0 MHz"), 103 | .phase_shift10("0 ps"), 104 | .duty_cycle10(50), 105 | .output_clock_frequency11("0 MHz"), 106 | .phase_shift11("0 ps"), 107 | .duty_cycle11(50), 108 | .output_clock_frequency12("0 MHz"), 109 | .phase_shift12("0 ps"), 110 | .duty_cycle12(50), 111 | .output_clock_frequency13("0 MHz"), 112 | .phase_shift13("0 ps"), 113 | .duty_cycle13(50), 114 | .output_clock_frequency14("0 MHz"), 115 | .phase_shift14("0 ps"), 116 | .duty_cycle14(50), 117 | .output_clock_frequency15("0 MHz"), 118 | .phase_shift15("0 ps"), 119 | .duty_cycle15(50), 120 | .output_clock_frequency16("0 MHz"), 121 | .phase_shift16("0 ps"), 122 | .duty_cycle16(50), 123 | .output_clock_frequency17("0 MHz"), 124 | .phase_shift17("0 ps"), 125 | .duty_cycle17(50), 126 | .pll_type("General"), 127 | .pll_subtype("General") 128 | ) altera_pll_i ( 129 | .rst(i_rst), 130 | .outclk({o_clk7, o_clk6, o_clk5, o_clk4, o_clk3, o_clk2, o_clk1, o_clk0}), 131 | .locked(o_locked), 132 | .fboutclk( ), 133 | .fbclk(1'b0), 134 | .refclk(i_refclk) 135 | ); 136 | endmodule 137 | 138 | -------------------------------------------------------------------------------- /src/rtl/de10_lite/pll.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2020 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is an Intel flavor "altpll" PLL. 22 | ---------------------------------------------------------------------------------------------------- 23 | 24 | library ieee; 25 | use ieee.std_logic_1164.all; 26 | library altera_mf; 27 | use altera_mf.all; 28 | 29 | entity pll is 30 | generic( 31 | CLK_MUL : positive; 32 | CLK_DIV : positive 33 | ); 34 | port( 35 | i_rst : in std_logic; 36 | i_refclk : in std_logic; 37 | o_clk : out std_logic; 38 | o_locked : out std_logic 39 | ); 40 | end pll; 41 | 42 | architecture rtl of pll is 43 | signal s_in_clocks : std_logic_vector (1 downto 0); 44 | signal s_gen_clocks : std_logic_vector (4 downto 0); 45 | 46 | component altpll 47 | generic( 48 | bandwidth_type : string; 49 | clk0_divide_by : natural; 50 | clk0_duty_cycle : natural; 51 | clk0_multiply_by : natural; 52 | clk0_phase_shift : string; 53 | compensate_clock : string; 54 | inclk0_input_frequency : natural; 55 | intended_device_family : string; 56 | lpm_hint : string; 57 | lpm_type : string; 58 | operation_mode : string; 59 | pll_type : string; 60 | port_activeclock : string; 61 | port_areset : string; 62 | port_clkbad0 : string; 63 | port_clkbad1 : string; 64 | port_clkloss : string; 65 | port_clkswitch : string; 66 | port_configupdate : string; 67 | port_fbin : string; 68 | port_inclk0 : string; 69 | port_inclk1 : string; 70 | port_locked : string; 71 | port_pfdena : string; 72 | port_phasecounterselect : string; 73 | port_phasedone : string; 74 | port_phasestep : string; 75 | port_phaseupdown : string; 76 | port_pllena : string; 77 | port_scanaclr : string; 78 | port_scanclk : string; 79 | port_scanclkena : string; 80 | port_scandata : string; 81 | port_scandataout : string; 82 | port_scandone : string; 83 | port_scanread : string; 84 | port_scanwrite : string; 85 | port_clk0 : string; 86 | port_clk1 : string; 87 | port_clk2 : string; 88 | port_clk3 : string; 89 | port_clk4 : string; 90 | port_clk5 : string; 91 | port_clkena0 : string; 92 | port_clkena1 : string; 93 | port_clkena2 : string; 94 | port_clkena3 : string; 95 | port_clkena4 : string; 96 | port_clkena5 : string; 97 | port_extclk0 : string; 98 | port_extclk1 : string; 99 | port_extclk2 : string; 100 | port_extclk3 : string; 101 | self_reset_on_loss_lock : string; 102 | width_clock : natural 103 | ); 104 | port( 105 | areset : in std_logic; 106 | inclk : in std_logic_vector (1 downto 0); 107 | clk : out std_logic_vector (4 downto 0); 108 | locked : out std_logic 109 | ); 110 | end component; 111 | begin 112 | s_in_clocks <= "0" & i_refclk; 113 | o_clk <= s_gen_clocks(0); 114 | 115 | altpll_component : altpll 116 | generic map ( 117 | bandwidth_type => "AUTO", 118 | 119 | clk0_divide_by => CLK_DIV, 120 | clk0_multiply_by => CLK_MUL, 121 | clk0_duty_cycle => 50, 122 | clk0_phase_shift => "0", 123 | 124 | compensate_clock => "CLK0", 125 | inclk0_input_frequency => 20000, 126 | intended_device_family => "MAX 10", 127 | lpm_hint => "CBX_MODULE_PREFIX=mypll", 128 | lpm_type => "altpll", 129 | operation_mode => "NORMAL", 130 | pll_type => "AUTO", 131 | port_activeclock => "PORT_UNUSED", 132 | port_areset => "PORT_USED", 133 | port_clkbad0 => "PORT_UNUSED", 134 | port_clkbad1 => "PORT_UNUSED", 135 | port_clkloss => "PORT_UNUSED", 136 | port_clkswitch => "PORT_UNUSED", 137 | port_configupdate => "PORT_UNUSED", 138 | port_fbin => "PORT_UNUSED", 139 | port_inclk0 => "PORT_USED", 140 | port_inclk1 => "PORT_UNUSED", 141 | port_locked => "PORT_USED", 142 | port_pfdena => "PORT_UNUSED", 143 | port_phasecounterselect => "PORT_UNUSED", 144 | port_phasedone => "PORT_UNUSED", 145 | port_phasestep => "PORT_UNUSED", 146 | port_phaseupdown => "PORT_UNUSED", 147 | port_pllena => "PORT_UNUSED", 148 | port_scanaclr => "PORT_UNUSED", 149 | port_scanclk => "PORT_UNUSED", 150 | port_scanclkena => "PORT_UNUSED", 151 | port_scandata => "PORT_UNUSED", 152 | port_scandataout => "PORT_UNUSED", 153 | port_scandone => "PORT_UNUSED", 154 | port_scanread => "PORT_UNUSED", 155 | port_scanwrite => "PORT_UNUSED", 156 | port_clk0 => "PORT_USED", 157 | port_clk1 => "PORT_UNUSED", 158 | port_clk2 => "PORT_UNUSED", 159 | port_clk3 => "PORT_UNUSED", 160 | port_clk4 => "PORT_UNUSED", 161 | port_clk5 => "PORT_UNUSED", 162 | port_clkena0 => "PORT_UNUSED", 163 | port_clkena1 => "PORT_UNUSED", 164 | port_clkena2 => "PORT_UNUSED", 165 | port_clkena3 => "PORT_UNUSED", 166 | port_clkena4 => "PORT_UNUSED", 167 | port_clkena5 => "PORT_UNUSED", 168 | port_extclk0 => "PORT_UNUSED", 169 | port_extclk1 => "PORT_UNUSED", 170 | port_extclk2 => "PORT_UNUSED", 171 | port_extclk3 => "PORT_UNUSED", 172 | self_reset_on_loss_lock => "OFF", 173 | width_clock => 5 174 | ) 175 | port map ( 176 | areset => i_rst, 177 | inclk => s_in_clocks, 178 | clk => s_gen_clocks, 179 | locked => o_locked 180 | ); 181 | end rtl; 182 | -------------------------------------------------------------------------------- /src/rtl/dither.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | library ieee; 21 | use ieee.std_logic_1164.all; 22 | use ieee.numeric_std.all; 23 | 24 | entity dither is 25 | generic( 26 | BITS_R : positive; 27 | BITS_G : positive; 28 | BITS_B : positive 29 | ); 30 | port( 31 | i_rst : in std_logic; 32 | i_clk : in std_logic; 33 | i_method : in std_logic_vector(1 downto 0); 34 | i_r : in std_logic_vector(7 downto 0); 35 | i_g : in std_logic_vector(7 downto 0); 36 | i_b : in std_logic_vector(7 downto 0); 37 | o_r : out std_logic_vector(BITS_R-1 downto 0); 38 | o_g : out std_logic_vector(BITS_G-1 downto 0); 39 | o_b : out std_logic_vector(BITS_B-1 downto 0) 40 | ); 41 | end dither; 42 | 43 | architecture rtl of dither is 44 | constant C_DITHER_BITS_R : positive := 8 - BITS_R; 45 | constant C_DITHER_BITS_G : positive := 8 - BITS_G; 46 | constant C_DITHER_BITS_B : positive := 8 - BITS_B; 47 | 48 | constant C_METHOD_NONE : std_logic_vector(1 downto 0) := "00"; 49 | constant C_METHOD_WHITE : std_logic_vector(1 downto 0) := "01"; 50 | 51 | signal s_rnd_1 : std_logic_vector(7 downto 0); 52 | signal s_rnd_2 : std_logic_vector(7 downto 0); 53 | signal s_rnd_3 : std_logic_vector(7 downto 0); 54 | signal s_rnd_r : std_logic_vector(C_DITHER_BITS_R-1 downto 0); 55 | signal s_rnd_g : std_logic_vector(C_DITHER_BITS_G-1 downto 0); 56 | signal s_rnd_b : std_logic_vector(C_DITHER_BITS_B-1 downto 0); 57 | signal s_next_dither_r : std_logic_vector(C_DITHER_BITS_R-1 downto 0); 58 | signal s_next_dither_g : std_logic_vector(C_DITHER_BITS_G-1 downto 0); 59 | signal s_next_dither_b : std_logic_vector(C_DITHER_BITS_B-1 downto 0); 60 | signal s_dither_r : std_logic_vector(C_DITHER_BITS_R-1 downto 0); 61 | signal s_dither_g : std_logic_vector(C_DITHER_BITS_G-1 downto 0); 62 | signal s_dither_b : std_logic_vector(C_DITHER_BITS_B-1 downto 0); 63 | 64 | signal s_dither_r_ext : std_logic_vector(9 downto 0); 65 | signal s_dither_g_ext : std_logic_vector(9 downto 0); 66 | signal s_dither_b_ext : std_logic_vector(9 downto 0); 67 | signal s_next_r_unclamped : std_logic_vector(9 downto 0); 68 | signal s_next_g_unclamped : std_logic_vector(9 downto 0); 69 | signal s_next_b_unclamped : std_logic_vector(9 downto 0); 70 | signal s_r_unclamped : std_logic_vector(9 downto 0); 71 | signal s_g_unclamped : std_logic_vector(9 downto 0); 72 | signal s_b_unclamped : std_logic_vector(9 downto 0); 73 | 74 | signal s_next_r : std_logic_vector(BITS_R-1 downto 0); 75 | signal s_next_g : std_logic_vector(BITS_G-1 downto 0); 76 | signal s_next_b : std_logic_vector(BITS_B-1 downto 0); 77 | 78 | function clamp_and_trunc(x : std_logic_vector; bits : integer) return std_logic_vector is 79 | begin 80 | if x(9) = '1' then 81 | -- Underflow -> 0 82 | return std_logic_vector(to_signed(0, bits)); 83 | elsif x(8) = '1' then 84 | -- Overflow -> "11...11" 85 | return std_logic_vector(to_signed(-1, bits)); 86 | else 87 | return x(7 downto 8-bits); 88 | end if; 89 | end function; 90 | 91 | begin 92 | -------------------------------------------------------------------------------------------------- 93 | -- Stage 1: Generate dithering noise. 94 | -------------------------------------------------------------------------------------------------- 95 | 96 | -- We use three PRNG:s to generate enough entropy for the dithering logic. 97 | prng1: entity work.prng 98 | generic map ( 99 | START_VALUE => x"8654af40" 100 | ) 101 | port map ( 102 | i_rst => i_rst, 103 | i_clk => i_clk, 104 | o_rnd => s_rnd_1 105 | ); 106 | prng2: entity work.prng 107 | generic map ( 108 | START_VALUE => x"a654f813" 109 | ) 110 | port map ( 111 | i_rst => i_rst, 112 | i_clk => i_clk, 113 | o_rnd => s_rnd_2 114 | ); 115 | prng3: entity work.prng 116 | generic map ( 117 | START_VALUE => x"54543844" 118 | ) 119 | port map ( 120 | i_rst => i_rst, 121 | i_clk => i_clk, 122 | o_rnd => s_rnd_3 123 | ); 124 | 125 | -- Extract different random numbers for R, G and B. 126 | s_rnd_r <= s_rnd_1(7 downto (8 - C_DITHER_BITS_R)); 127 | s_rnd_g <= s_rnd_2(7 downto (8 - C_DITHER_BITS_G)); 128 | s_rnd_b <= s_rnd_3(7 downto (8 - C_DITHER_BITS_B)); 129 | 130 | -- Select dithering type. 131 | DitherMuxR: with i_method select 132 | s_next_dither_r <= s_rnd_r when C_METHOD_WHITE, 133 | (others => '0') when others; 134 | 135 | DitherMuxG: with i_method select 136 | s_next_dither_g <= s_rnd_g when C_METHOD_WHITE, 137 | (others => '0') when others; 138 | 139 | DitherMuxB: with i_method select 140 | s_next_dither_b <= s_rnd_b when C_METHOD_WHITE, 141 | (others => '0') when others; 142 | 143 | process(i_rst, i_clk) 144 | begin 145 | if i_rst = '1' then 146 | s_dither_r <= (others => '0'); 147 | s_dither_g <= (others => '0'); 148 | s_dither_b <= (others => '0'); 149 | elsif rising_edge(i_clk) then 150 | s_dither_r <= s_next_dither_r; 151 | s_dither_g <= s_next_dither_g; 152 | s_dither_b <= s_next_dither_b; 153 | end if; 154 | end process; 155 | 156 | 157 | -------------------------------------------------------------------------------------------------- 158 | -- Stage 2: Apply dithering. 159 | -------------------------------------------------------------------------------------------------- 160 | 161 | -- Sign extend dithering data. 162 | s_dither_r_ext(C_DITHER_BITS_R-1 downto 0) <= s_dither_r; 163 | s_dither_r_ext(9 downto C_DITHER_BITS_R) <= (others => s_dither_r(C_DITHER_BITS_R-1)); 164 | s_dither_g_ext(C_DITHER_BITS_G-1 downto 0) <= s_dither_g; 165 | s_dither_g_ext(9 downto C_DITHER_BITS_G) <= (others => s_dither_g(C_DITHER_BITS_G-1)); 166 | s_dither_b_ext(C_DITHER_BITS_B-1 downto 0) <= s_dither_b; 167 | s_dither_b_ext(9 downto C_DITHER_BITS_B) <= (others => s_dither_b(C_DITHER_BITS_B-1)); 168 | 169 | -- Perform dithering. 170 | s_next_r_unclamped <= std_logic_vector(signed("00" & i_r) + signed(s_dither_r_ext)); 171 | s_next_g_unclamped <= std_logic_vector(signed("00" & i_g) + signed(s_dither_g_ext)); 172 | s_next_b_unclamped <= std_logic_vector(signed("00" & i_b) + signed(s_dither_b_ext)); 173 | 174 | process(i_rst, i_clk) 175 | begin 176 | if i_rst = '1' then 177 | s_r_unclamped <= (others => '0'); 178 | s_g_unclamped <= (others => '0'); 179 | s_b_unclamped <= (others => '0'); 180 | elsif rising_edge(i_clk) then 181 | s_r_unclamped <= s_next_r_unclamped; 182 | s_g_unclamped <= s_next_g_unclamped; 183 | s_b_unclamped <= s_next_b_unclamped; 184 | end if; 185 | end process; 186 | 187 | 188 | -------------------------------------------------------------------------------------------------- 189 | -- Stage 3: Clamp and truncate. 190 | -------------------------------------------------------------------------------------------------- 191 | 192 | -- Form the final result via clamping and truncation. 193 | s_next_r <= clamp_and_trunc(s_r_unclamped, BITS_R); 194 | s_next_g <= clamp_and_trunc(s_g_unclamped, BITS_G); 195 | s_next_b <= clamp_and_trunc(s_b_unclamped, BITS_B); 196 | 197 | process(i_rst, i_clk) 198 | begin 199 | if i_rst = '1' then 200 | o_r <= (others => '0'); 201 | o_g <= (others => '0'); 202 | o_b <= (others => '0'); 203 | elsif rising_edge(i_clk) then 204 | o_r <= s_next_r; 205 | o_g <= s_next_g; 206 | o_b <= s_next_b; 207 | end if; 208 | end process; 209 | end rtl; 210 | 211 | -------------------------------------------------------------------------------- /src/rtl/fifo.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2023 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- Based on "Register based FIFO" by nandland: https://nandland.com/register-based-fifo/ 22 | ---------------------------------------------------------------------------------------------------- 23 | 24 | library ieee; 25 | use ieee.std_logic_1164.all; 26 | use ieee.numeric_std.all; 27 | 28 | entity fifo is 29 | generic ( 30 | G_WIDTH : integer := 32; 31 | G_DEPTH : integer := 16 32 | ); 33 | port ( 34 | -- Control signals. 35 | i_rst : in std_logic; 36 | i_clk : in std_logic; 37 | 38 | -- FIFO Write Interface. 39 | i_wr_en : in std_logic; 40 | i_wr_data : in std_logic_vector(G_WIDTH-1 downto 0); 41 | o_full : out std_logic; 42 | 43 | -- FIFO Read Interface. 44 | i_rd_en : in std_logic; 45 | o_rd_data : out std_logic_vector(G_WIDTH-1 downto 0); 46 | o_empty : out std_logic 47 | ); 48 | end fifo; 49 | 50 | architecture rtl of fifo is 51 | type T_FIFO_DATA is array (0 to G_DEPTH-1) of std_logic_vector(G_WIDTH-1 downto 0); 52 | signal s_fifo_data : T_FIFO_DATA := (others => (others => '0')); 53 | 54 | -- Ensure that the FIFO data is using registers, not BRAM. 55 | attribute RAMSTYLE : string; 56 | attribute RAMSTYLE of s_fifo_data : signal is "MLAB"; -- Intel/Altera 57 | attribute RAM_STYLE : string; 58 | attribute RAM_STYLE of s_fifo_data : signal is "distributed"; -- Xilinx 59 | 60 | signal s_wr_idx : integer range 0 to G_DEPTH-1 := 0; 61 | signal s_rd_idx : integer range 0 to G_DEPTH-1 := 0; 62 | signal s_fifo_count : integer range 0 to G_DEPTH := 0; 63 | 64 | signal s_full : std_logic; 65 | signal s_empty : std_logic; 66 | begin 67 | process (i_rst, i_clk) is 68 | begin 69 | if i_rst = '1' then 70 | s_fifo_count <= 0; 71 | s_wr_idx <= 0; 72 | s_rd_idx <= 0; 73 | elsif rising_edge(i_clk) then 74 | -- Keeps track of the total number of words in the FIFO. 75 | if i_wr_en = '1' and i_rd_en = '0' then 76 | s_fifo_count <= s_fifo_count + 1; 77 | elsif i_wr_en = '0' and i_rd_en = '1' then 78 | s_fifo_count <= s_fifo_count - 1; 79 | end if; 80 | 81 | -- Keeps track of the write index (and controls roll-over). 82 | if i_wr_en = '1' and s_full = '0' then 83 | if s_wr_idx = G_DEPTH - 1 then 84 | s_wr_idx <= 0; 85 | else 86 | s_wr_idx <= s_wr_idx + 1; 87 | end if; 88 | end if; 89 | 90 | -- Keeps track of the read index (and controls roll-over). 91 | if i_rd_en = '1' and s_empty = '0' then 92 | if s_rd_idx = G_DEPTH - 1 then 93 | s_rd_idx <= 0; 94 | else 95 | s_rd_idx <= s_rd_idx + 1; 96 | end if; 97 | end if; 98 | 99 | -- Registers the input data when there is a write. 100 | if i_wr_en = '1' then 101 | s_fifo_data(s_wr_idx) <= i_wr_data; 102 | end if; 103 | end if; 104 | end process; 105 | 106 | o_rd_data <= s_fifo_data(s_rd_idx); 107 | 108 | -- TODO(m): Make the full/empty signals registered. 109 | s_full <= '1' when s_fifo_count = G_DEPTH else '0'; 110 | s_empty <= '1' when s_fifo_count = 0 else '0'; 111 | 112 | o_full <= s_full; 113 | o_empty <= s_empty; 114 | end rtl; 115 | -------------------------------------------------------------------------------- /src/rtl/mmio_types.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This file contains type definitions for MMIO registers. 22 | ---------------------------------------------------------------------------------------------------- 23 | 24 | library ieee; 25 | use ieee.std_logic_1164.all; 26 | 27 | package mmio_types is 28 | subtype T_MMIO_REG_WORD is std_logic_vector(31 downto 0); 29 | 30 | -------------------------------------------------------------------------------------------------- 31 | -- Read-only registers. 32 | -------------------------------------------------------------------------------------------------- 33 | type T_MMIO_REGS_RO is record 34 | -- MC1 internal registers. 35 | CLKCNTLO : T_MMIO_REG_WORD; -- CPU clock cycle count - lo bits (free running counter). 36 | CLKCNTHI : T_MMIO_REG_WORD; -- CPU clock cycle count - hi bits (free running counter). 37 | CPUCLK : T_MMIO_REG_WORD; -- CPU clock frequency in Hz. 38 | VRAMSIZE : T_MMIO_REG_WORD; -- VRAM size in bytes. 39 | XRAMSIZE : T_MMIO_REG_WORD; -- Extended RAM size in bytes. 40 | VIDWIDTH : T_MMIO_REG_WORD; -- Native video resoltuion, width. 41 | VIDHEIGHT : T_MMIO_REG_WORD; -- Native video resoltuion, height. 42 | VIDFPS : T_MMIO_REG_WORD; -- Video refresh rate in 65536 * frames per s. 43 | VIDFRAMENO : T_MMIO_REG_WORD; -- Video frame number (free running counter). 44 | VIDY : T_MMIO_REG_WORD; -- Video raster Y position. 45 | 46 | -- External registers. 47 | -- TODO(m): microSD inputs, GPIO inputs. 48 | SWITCHES : T_MMIO_REG_WORD; -- Switches (one bit per switch, active high). 49 | BUTTONS : T_MMIO_REG_WORD; -- Buttons (one bit per button, active high). 50 | KEYPTR : T_MMIO_REG_WORD; -- Keyboard event buffer pointer. 51 | MOUSEPOS : T_MMIO_REG_WORD; -- Mouse position (x & y coord in upper & lower 16 bits) 52 | MOUSEBTNS : T_MMIO_REG_WORD; -- Mouse buttons (left, middle, right in bits 0, 1, 2) 53 | SDIN : T_MMIO_REG_WORD; -- SD card input: 54 | -- 0: DAT0/MISO 55 | -- 1: DAT1 56 | -- 2: DAT2 57 | -- 3: DAT3/SS* 58 | -- 4: CMD/MOSI 59 | end record T_MMIO_REGS_RO; 60 | 61 | -------------------------------------------------------------------------------------------------- 62 | -- Write-only registers. 63 | -------------------------------------------------------------------------------------------------- 64 | type T_MMIO_REGS_WO is record 65 | -- MC1 internal registers. 66 | -- TODO(m): Add something here? 67 | 68 | -- External registers. 69 | -- TODO(m): microSD outputs, GPIO outputs. 70 | SEGDISP0 : T_MMIO_REG_WORD; -- Segmented display 0 (one bit per segment, active high). 71 | SEGDISP1 : T_MMIO_REG_WORD; -- Segmented display 1 (one bit per segment, active high). 72 | SEGDISP2 : T_MMIO_REG_WORD; -- Segmented display 2 (one bit per segment, active high). 73 | SEGDISP3 : T_MMIO_REG_WORD; -- Segmented display 3 (one bit per segment, active high). 74 | SEGDISP4 : T_MMIO_REG_WORD; -- Segmented display 4 (one bit per segment, active high). 75 | SEGDISP5 : T_MMIO_REG_WORD; -- Segmented display 5 (one bit per segment, active high). 76 | SEGDISP6 : T_MMIO_REG_WORD; -- Segmented display 6 (one bit per segment, active high). 77 | SEGDISP7 : T_MMIO_REG_WORD; -- Segmented display 7 (one bit per segment, active high). 78 | LEDS : T_MMIO_REG_WORD; -- LED:s (one bit per LED, active high). 79 | SDOUT : T_MMIO_REG_WORD; -- SD card output: 80 | -- 0: DAT0/MISO 81 | -- 1: DAT1 82 | -- 2: DAT2 83 | -- 3: DAT3/SS* 84 | -- 4: CMD/MOSI 85 | -- 5: CLK/SCK (always unmasked) 86 | SDWE : T_MMIO_REG_WORD; -- SD card write enable bit mask (bits 0-4). 87 | 88 | end record T_MMIO_REGS_WO; 89 | end package; 90 | -------------------------------------------------------------------------------- /src/rtl/prng.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | library ieee; 21 | use ieee.std_logic_1164.all; 22 | 23 | 24 | ---------------------------------------------------------------------------------------------------- 25 | -- This is pseudo-random number generator (PRNG) that is implemented as a Linear Feedback Shift 26 | -- Register (LFSR). 27 | -- 28 | -- Since the PRNG is primarily intended to be used for dithering the video signal, we need a fairly 29 | -- long sequence (before repeating the signal) to avoid nasty spatial or temporal patterns. A quick 30 | -- estimation of the required sequence lenght is (for 1080p HD): 31 | -- 32 | -- 2475000 pixels/frame x 60 FPS x 10 seconds = 1485000000 pixels / 10 s 33 | -- 34 | -- This number can be represented with 31 bits. Thus a 32-bit LFSR is sufficient. 35 | ---------------------------------------------------------------------------------------------------- 36 | 37 | entity prng is 38 | generic( 39 | START_VALUE : std_logic_vector(32 downto 1) := x"8654af40" 40 | ); 41 | port( 42 | i_rst : in std_logic; 43 | i_clk : in std_logic; 44 | o_rnd : out std_logic_vector(7 downto 0) 45 | ); 46 | end prng; 47 | 48 | architecture rtl of prng is 49 | signal s_state : std_logic_vector(32 downto 1); 50 | signal s_feedback : std_logic; 51 | begin 52 | -- 32-bit LFSR taps: 32, 22, 2, 1 53 | s_feedback <= not (s_state(32) xor 54 | s_state(22) xor 55 | s_state(2) xor 56 | s_state(1)); 57 | 58 | process(i_rst, i_clk) 59 | begin 60 | if i_rst = '1' then 61 | s_state <= START_VALUE; 62 | elsif rising_edge(i_clk) then 63 | s_state <= s_state(31 downto 1) & s_feedback; 64 | end if; 65 | end process; 66 | 67 | -- Output 8 "kind'a independent" PRN bits. 68 | o_rnd(0) <= s_state(32); 69 | o_rnd(1) <= s_state(7) xor s_state(12); 70 | o_rnd(2) <= s_state(27); 71 | o_rnd(3) <= s_state(17) xor s_state(4); 72 | o_rnd(4) <= s_state(11); 73 | o_rnd(5) <= s_state(31) xor s_state(2); 74 | o_rnd(6) <= s_state(23); 75 | o_rnd(7) <= s_state(1) xor s_state(16); 76 | end rtl; 77 | 78 | -------------------------------------------------------------------------------- /src/rtl/ps2_keyboard.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2020 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a PS/2 keyboard interface. 22 | ---------------------------------------------------------------------------------------------------- 23 | 24 | library ieee; 25 | use ieee.std_logic_1164.all; 26 | 27 | entity ps2_keyboard is 28 | generic( 29 | CLK_FREQ : integer -- System clock frequency in Hz. 30 | ); 31 | port( 32 | -- System (CPU) control signals. 33 | i_rst : in std_logic; 34 | i_clk : in std_logic; 35 | 36 | -- PS/2 bus signals. 37 | i_ps2_clk : in std_logic; 38 | i_ps2_data : in std_logic; 39 | 40 | -- Keyboard output. 41 | o_scancode : out std_logic_vector(8 downto 0); 42 | o_press : out std_logic; 43 | o_stb : out std_logic 44 | ); 45 | end ps2_keyboard; 46 | 47 | architecture rtl of ps2_keyboard is 48 | type STATE_T is (RESET, WAITING, LONGCODE, BREAK, DONE); 49 | 50 | signal s_data : std_logic_vector(7 downto 0); 51 | signal s_data_stb : std_logic; 52 | 53 | signal s_state : STATE_T; 54 | signal s_is_break : std_logic; 55 | signal s_is_long : std_logic; 56 | signal s_scancode : std_logic_vector(7 downto 0); 57 | begin 58 | -- Instantiate the PS/2 interface. 59 | ps2_if: entity work.ps2_receiver 60 | generic map ( 61 | CLK_FREQ => CLK_FREQ 62 | ) 63 | port map ( 64 | i_rst => i_rst, 65 | i_clk => i_clk, 66 | i_ps2_clk => i_ps2_clk, 67 | i_ps2_data => i_ps2_data, 68 | o_data => s_data, 69 | o_data_stb => s_data_stb 70 | ); 71 | 72 | -- Collect key scancode events. 73 | process(i_rst, i_clk) 74 | begin 75 | if i_rst = '1' then 76 | s_state <= RESET; 77 | s_is_break <= '0'; 78 | s_is_long <= '0'; 79 | s_scancode <= (others => '0'); 80 | o_scancode <= (others => '0'); 81 | o_press <= '0'; 82 | o_stb <= '0'; 83 | elsif rising_edge(i_clk) then 84 | -- FSM. 85 | case s_state is 86 | when RESET => 87 | -- TODO(m): Add a keyboard reset cycle (e.g. send 0xFF - Reset, 0xED - Set/Reset LEDs). 88 | o_stb <= '0'; 89 | s_state <= WAITING; 90 | 91 | when WAITING => 92 | o_stb <= '0'; 93 | s_is_break <= '0'; 94 | s_is_long <= '0'; 95 | if s_data_stb = '1' then 96 | s_scancode <= s_data; 97 | if s_data = x"e0" then 98 | s_state <= LONGCODE; 99 | elsif s_data = x"f0" then 100 | s_state <= BREAK; 101 | else 102 | s_state <= DONE; 103 | end if; 104 | end if; 105 | 106 | when LONGCODE => 107 | s_is_long <= '1'; 108 | if s_data_stb = '1' then 109 | s_scancode <= s_data; 110 | if s_data = x"f0" then 111 | s_state <= BREAK; 112 | else 113 | s_state <= DONE; 114 | end if; 115 | end if; 116 | 117 | when BREAK => 118 | s_is_break <= '1'; 119 | if s_data_stb = '1' then 120 | s_scancode <= s_data; 121 | s_state <= DONE; 122 | end if; 123 | 124 | when DONE => 125 | o_scancode <= s_is_long & s_scancode; 126 | o_press <= not s_is_break; 127 | o_stb <= '1'; 128 | s_state <= WAITING; 129 | 130 | when others => 131 | o_stb <= '0'; 132 | s_state <= WAITING; 133 | end case; 134 | end if; 135 | end process; 136 | end rtl; 137 | -------------------------------------------------------------------------------- /src/rtl/ps2_receiver.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2020 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a very simple PS/2 receiver that receives data from an input device such as a keyboard. 22 | ---------------------------------------------------------------------------------------------------- 23 | 24 | library ieee; 25 | use ieee.std_logic_1164.all; 26 | 27 | entity ps2_receiver is 28 | generic( 29 | CLK_FREQ : integer -- System clock frequency in Hz. 30 | ); 31 | port( 32 | -- System (CPU) control signals. 33 | i_rst : in std_logic; 34 | i_clk : in std_logic; 35 | 36 | -- PS/2 bus signals. 37 | i_ps2_clk : in std_logic; 38 | i_ps2_data : in std_logic; 39 | 40 | -- Deserialized output. 41 | o_data : out std_logic_vector(7 downto 0); 42 | o_data_stb : out std_logic 43 | ); 44 | end ps2_receiver; 45 | 46 | architecture rtl of ps2_receiver is 47 | constant C_CLK_DEBOUNCE_COUNT : integer := CLK_FREQ / 100000; -- 10 us 48 | constant C_DATA_DEBOUNCE_COUNT : integer := CLK_FREQ / 1000000; -- 1 us 49 | constant C_MAX_IDLE_COUNT : integer := CLK_FREQ / 18000; -- 55.6 us 50 | constant C_FRAME_BITS : integer := 11; 51 | 52 | type STATE_T is (RESEND, IDLE, RECEIVING, DONE); 53 | 54 | signal s_state : STATE_T; 55 | signal s_ps2_clk_int : std_logic; 56 | signal s_prev_ps2_clk_int : std_logic; 57 | signal s_ps2_data_int : std_logic; 58 | signal s_ps2_frame : std_logic_vector(C_FRAME_BITS-1 downto 0); 59 | signal s_bit_count : integer range 0 to C_FRAME_BITS; 60 | signal s_idle_count : integer range 0 to C_MAX_IDLE_COUNT; 61 | 62 | function is_frame_ok(frame : std_logic_vector) return boolean is 63 | variable v_start_bit : std_logic; 64 | variable v_parity : std_logic; 65 | variable v_stop_bit : std_logic; 66 | begin 67 | v_start_bit := frame(0); 68 | v_parity := frame(1) xor frame(2) xor frame(3) xor frame(4) xor 69 | frame(5) xor frame(6) xor frame(7) xor frame(8) xor frame(9); 70 | v_stop_bit := frame(10); 71 | return v_start_bit = '0' and v_parity = '1' and v_stop_bit = '1'; 72 | end function; 73 | begin 74 | -- Synchronize the PS/2 signals to the system clock domain. 75 | sync_ps2_clk: entity work.bit_synchronizer 76 | generic map ( 77 | STEADY_CYCLES => C_CLK_DEBOUNCE_COUNT 78 | ) 79 | port map ( 80 | i_rst => i_rst, 81 | i_clk => i_clk, 82 | i_d => i_ps2_clk, 83 | o_q => s_ps2_clk_int 84 | ); 85 | sync_ps2_data: entity work.bit_synchronizer 86 | generic map ( 87 | STEADY_CYCLES => C_DATA_DEBOUNCE_COUNT 88 | ) 89 | port map ( 90 | i_rst => i_rst, 91 | i_clk => i_clk, 92 | i_d => i_ps2_data, 93 | o_q => s_ps2_data_int 94 | ); 95 | 96 | -- Deserialize the PS/2 signal. 97 | process(i_rst, i_clk) 98 | variable v_ps2_clk_falling_edge : boolean; 99 | begin 100 | if i_rst = '1' then 101 | s_state <= IDLE; 102 | s_ps2_frame <= (others => '0'); 103 | s_prev_ps2_clk_int <= '0'; 104 | s_idle_count <= 0; 105 | o_data <= (others => '0'); 106 | o_data_stb <= '0'; 107 | elsif rising_edge(i_clk) then 108 | -- Detect falling edges on i_ps2_clk. 109 | v_ps2_clk_falling_edge := (s_ps2_clk_int = '0' and s_prev_ps2_clk_int = '1'); 110 | s_prev_ps2_clk_int <= s_ps2_clk_int; 111 | 112 | -- FSM. 113 | case s_state is 114 | when IDLE => 115 | o_data_stb <= '0'; 116 | s_idle_count <= 0; 117 | -- We start receiving on a falling clock edge if we have a start bit (0). 118 | if v_ps2_clk_falling_edge and s_ps2_data_int = '0' then 119 | s_ps2_frame <= s_ps2_data_int & s_ps2_frame(10 downto 1); 120 | -- s_ps2_frame <= "00000000000"; 121 | s_bit_count <= 1; 122 | s_state <= RECEIVING; 123 | end if; 124 | 125 | when RECEIVING => 126 | if v_ps2_clk_falling_edge then 127 | -- Shift in a new bit from the left. 128 | s_ps2_frame <= s_ps2_data_int & s_ps2_frame(10 downto 1); 129 | s_bit_count <= s_bit_count + 1; 130 | s_idle_count <= 0; 131 | elsif s_idle_count = C_MAX_IDLE_COUNT then 132 | -- We haven't seen new data cycles for some time, so check if we got a complete 133 | -- and correct data frame. 134 | if s_bit_count < C_FRAME_BITS then 135 | -- We don't have all the bits yet, so wait some more (error?). 136 | s_idle_count <= 0; 137 | elsif s_bit_count = C_FRAME_BITS and is_frame_ok(s_ps2_frame) then 138 | s_state <= DONE; 139 | else 140 | -- Error! 141 | s_state <= RESEND; 142 | end if; 143 | else 144 | -- Count the number of cycles since the last falling edge. 145 | s_idle_count <= s_idle_count + 1; 146 | end if; 147 | 148 | when DONE => 149 | -- Extract the data payload from the frame, and strobe the o_data_stb signal. 150 | o_data <= s_ps2_frame(8 downto 1); 151 | o_data_stb <= '1'; 152 | s_state <= IDLE; 153 | 154 | when RESEND => 155 | -- TODO(m): Add a resend request. 156 | o_data_stb <= '0'; 157 | s_state <= IDLE; 158 | 159 | when others => 160 | o_data_stb <= '0'; 161 | s_state <= IDLE; 162 | end case; 163 | end if; 164 | end process; 165 | end rtl; 166 | -------------------------------------------------------------------------------- /src/rtl/ram_true_dual_port.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a true dual-port RAM implementation that should infer block RAM:s in FPGA:s. It only 22 | -- supports writing on port A - port B is read only. 23 | -- Inspired by: https://danstrother.com/2010/09/11/inferring-rams-in-fpgas/ 24 | -- 25 | -- This implementation requires support for VHDL-2000 protected shared variables (e.g. supported by 26 | -- GHDL). 27 | ---------------------------------------------------------------------------------------------------- 28 | 29 | library ieee; 30 | use ieee.std_logic_1164.all; 31 | use ieee.numeric_std.all; 32 | 33 | entity ram_true_dual_port is 34 | generic( 35 | DATA_BITS : integer := 32; 36 | ADR_BITS : integer := 10 37 | ); 38 | port( 39 | -- Port A 40 | i_clk_a : in std_logic; 41 | i_we_a : in std_logic; 42 | i_adr_a : in std_logic_vector(ADR_BITS-1 downto 0); 43 | i_data_a : in std_logic_vector(DATA_BITS-1 downto 0); 44 | o_data_a : out std_logic_vector(DATA_BITS-1 downto 0); 45 | 46 | -- Port B 47 | i_clk_b : in std_logic; 48 | i_adr_b : in std_logic_vector(ADR_BITS-1 downto 0); 49 | o_data_b : out std_logic_vector(DATA_BITS-1 downto 0) 50 | ); 51 | end ram_true_dual_port; 52 | 53 | architecture rtl of ram_true_dual_port is 54 | subtype T_ADDR is std_logic_vector(ADR_BITS-1 downto 0); 55 | subtype T_WORD is std_logic_vector(DATA_BITS-1 downto 0); 56 | 57 | type T_MEM is protected 58 | impure function read(addr : T_ADDR) return T_WORD; 59 | procedure write(addr : T_ADDR; data : T_WORD); 60 | end protected T_MEM; 61 | 62 | type T_MEM is protected body 63 | type T_MEM_ARRAY is array ((2**ADR_BITS)-1 downto 0) of T_WORD; 64 | 65 | variable v_mem_array : T_MEM_ARRAY; 66 | 67 | impure function read(addr : T_ADDR) return T_WORD is 68 | begin 69 | return v_mem_array(to_integer(unsigned(addr))); 70 | end function read; 71 | 72 | procedure write(addr : T_ADDR; data : T_WORD) is 73 | begin 74 | v_mem_array(to_integer(unsigned(addr))) := data; 75 | end procedure write; 76 | end protected body T_MEM; 77 | 78 | shared variable v_mem : T_MEM; 79 | begin 80 | -- Port A 81 | process(i_clk_a) 82 | begin 83 | if rising_edge(i_clk_a) then 84 | if i_we_a = '1' then 85 | v_mem.write(i_adr_a, i_data_a); 86 | end if; 87 | o_data_a <= v_mem.read(i_adr_a); 88 | end if; 89 | end process; 90 | 91 | -- Port B 92 | process(i_clk_b) 93 | begin 94 | if rising_edge(i_clk_b) then 95 | o_data_b <= v_mem.read(i_adr_b); 96 | end if; 97 | end process; 98 | end rtl; 99 | -------------------------------------------------------------------------------- /src/rtl/ram_true_dual_port_vhdl93.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a true dual-port RAM implementation that should infer block RAM:s in FPGA:s. It only 22 | -- supports writing on port A - port B is read only. 23 | -- Inspired by: https://danstrother.com/2010/09/11/inferring-rams-in-fpgas/ 24 | -- 25 | -- This implementation requires support for VHDL'93 shared variables (e.g. supported by Quartus). 26 | ---------------------------------------------------------------------------------------------------- 27 | 28 | library ieee; 29 | use ieee.std_logic_1164.all; 30 | use ieee.numeric_std.all; 31 | 32 | entity ram_true_dual_port is 33 | generic( 34 | DATA_BITS : integer := 32; 35 | ADR_BITS : integer := 10 36 | ); 37 | port( 38 | -- Port A 39 | i_clk_a : in std_logic; 40 | i_we_a : in std_logic; 41 | i_adr_a : in std_logic_vector(ADR_BITS-1 downto 0); 42 | i_data_a : in std_logic_vector(DATA_BITS-1 downto 0); 43 | o_data_a : out std_logic_vector(DATA_BITS-1 downto 0); 44 | 45 | -- Port B 46 | i_clk_b : in std_logic; 47 | i_adr_b : in std_logic_vector(ADR_BITS-1 downto 0); 48 | o_data_b : out std_logic_vector(DATA_BITS-1 downto 0) 49 | ); 50 | end ram_true_dual_port; 51 | 52 | architecture rtl of ram_true_dual_port is 53 | type T_MEM_ARRAY is array ((2**ADR_BITS)-1 downto 0) of std_logic_vector(DATA_BITS-1 downto 0); 54 | shared variable v_mem_array : T_MEM_ARRAY; 55 | begin 56 | -- Port A 57 | process(i_clk_a) 58 | begin 59 | if rising_edge(i_clk_a) then 60 | if i_we_a = '1' then 61 | v_mem_array(to_integer(unsigned(i_adr_a))) := i_data_a; 62 | end if; 63 | o_data_a <= v_mem_array(to_integer(unsigned(i_adr_a))); 64 | end if; 65 | end process; 66 | 67 | -- Port B 68 | process(i_clk_b) 69 | begin 70 | if rising_edge(i_clk_b) then 71 | o_data_b <= v_mem_array(to_integer(unsigned(i_adr_b))); 72 | end if; 73 | end process; 74 | end rtl; 75 | -------------------------------------------------------------------------------- /src/rtl/reset_conditioner.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a reset conditioner circuit for asynchronous reset. 22 | ---------------------------------------------------------------------------------------------------- 23 | 24 | library ieee; 25 | use ieee.std_logic_1164.all; 26 | 27 | entity reset_conditioner is 28 | port( 29 | -- Clock signal for the target clock domain. 30 | i_clk : in std_logic; 31 | 32 | -- Asynchronous reset signal. 33 | i_async_rst : in std_logic; 34 | 35 | -- Synchronized reset signal. 36 | o_rst : out std_logic 37 | ); 38 | end reset_conditioner; 39 | 40 | architecture rtl of reset_conditioner is 41 | signal s_rst_1 : std_logic; 42 | begin 43 | -- First flip-flop. 44 | process(i_clk, i_async_rst) 45 | begin 46 | if i_async_rst = '1' then 47 | s_rst_1 <= '1'; 48 | elsif rising_edge(i_clk) then 49 | s_rst_1 <= '0'; 50 | end if; 51 | end process; 52 | 53 | -- Second flip-flop. 54 | process(i_clk, i_async_rst) 55 | begin 56 | if i_async_rst = '1' then 57 | o_rst <= '1'; 58 | elsif rising_edge(i_clk) then 59 | o_rst <= s_rst_1; 60 | end if; 61 | end process; 62 | end rtl; 63 | -------------------------------------------------------------------------------- /src/rtl/reset_stabilizer.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a reset signal stabilizer (e.g. de-bouncing). 22 | ---------------------------------------------------------------------------------------------------- 23 | 24 | library ieee; 25 | use ieee.std_logic_1164.all; 26 | use ieee.numeric_std.all; 27 | 28 | entity reset_stabilizer is 29 | generic( 30 | -- With a 50 MHz clock, a 16 bit counter will wrap roughly every 1 ms. 31 | STABLE_COUNT_BITS : positive := 16 32 | ); 33 | port( 34 | i_rst_n : in std_logic; 35 | i_clk : in std_logic; 36 | o_rst : out std_logic 37 | ); 38 | end reset_stabilizer; 39 | 40 | architecture rtl of reset_stabilizer is 41 | constant C_ALL_ONES : unsigned(STABLE_COUNT_BITS-1 downto 0) := (others => '1'); 42 | 43 | signal s_conditioned_rst_1 : std_logic := '1'; 44 | signal s_conditioned_rst : std_logic := '1'; 45 | 46 | signal s_stable_rst_1 : std_logic := '1'; 47 | signal s_stable_rst : std_logic := '1'; 48 | signal s_stable_count : unsigned(STABLE_COUNT_BITS-1 downto 0) := to_unsigned(0, STABLE_COUNT_BITS); 49 | begin 50 | -- Two cascaded flip-flops to condition the source reset signal. 51 | process(i_clk, i_rst_n) 52 | begin 53 | if i_rst_n = '0' then 54 | s_conditioned_rst_1 <= '1'; 55 | s_conditioned_rst <= '1'; 56 | elsif rising_edge(i_clk) then 57 | s_conditioned_rst_1 <= '0'; 58 | s_conditioned_rst <= s_conditioned_rst_1; 59 | end if; 60 | end process; 61 | 62 | -- Counter 63 | process(i_clk, s_conditioned_rst) 64 | begin 65 | if s_conditioned_rst = '1' then 66 | s_stable_rst_1 <= '1'; 67 | s_stable_rst <= '1'; 68 | s_stable_count <= to_unsigned(0, STABLE_COUNT_BITS); 69 | elsif rising_edge(i_clk) then 70 | -- Deassert the first stable reset signal when the counter has reached its maximum value. 71 | if s_stable_count = C_ALL_ONES then 72 | s_stable_rst_1 <= '0'; 73 | end if; 74 | 75 | -- Cascade the stable reset through another register for good measure. 76 | s_stable_rst <= s_stable_rst_1; 77 | 78 | -- Increment the counter (wrapping). 79 | s_stable_count <= s_stable_count + 1; 80 | end if; 81 | end process; 82 | 83 | o_rst <= s_stable_rst; 84 | end rtl; 85 | 86 | -------------------------------------------------------------------------------- /src/rtl/synchronizer.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a two-flip-flop synchronization circuit for multi-bit signals. 22 | -- 23 | -- In addition to passing a signal over from one clock domain to another, this design also employs 24 | -- mitigations against timing differences between individual bits of the signal. This is done by 25 | -- detecting changes in the signal and only propagating the new signal value to the output once the 26 | -- signal has stayed constant for a certain number of clock cycles (this functionality is optional). 27 | ---------------------------------------------------------------------------------------------------- 28 | 29 | library ieee; 30 | use ieee.std_logic_1164.all; 31 | use ieee.numeric_std.all; 32 | 33 | entity synchronizer is 34 | generic( 35 | BITS : positive; 36 | STEADY_CYCLES : integer := 3 37 | ); 38 | port( 39 | i_rst : in std_logic; 40 | 41 | -- Clock signal for the target clock domain. 42 | i_clk : in std_logic; 43 | 44 | -- Signal from the source clock domain (or an asynchronous signal). 45 | i_d : in std_logic_vector(BITS-1 downto 0); 46 | 47 | -- Synchronized signal. 48 | o_q : out std_logic_vector(BITS-1 downto 0) 49 | ); 50 | end synchronizer; 51 | 52 | architecture rtl of synchronizer is 53 | -- Determine how many bits are required to represent an integer number 54 | -- (it is essentially log2()). 55 | function num_bits_required_for(x : integer) return positive is 56 | variable v_num_bits : positive; 57 | variable v_next_pot : positive; 58 | begin 59 | v_num_bits := 1; 60 | v_next_pot := 2; 61 | while x >= v_next_pot loop 62 | v_next_pot := v_next_pot * 2; 63 | v_num_bits := v_num_bits + 1; 64 | end loop; 65 | return v_num_bits; 66 | end; 67 | 68 | constant C_STEADY_CYCLES_BITS : positive := num_bits_required_for(STEADY_CYCLES); 69 | 70 | -- Signals for the synchronizer flip-flops. 71 | signal s_metastable : std_logic_vector(BITS-1 downto 0); 72 | signal s_stable : std_logic_vector(BITS-1 downto 0); 73 | 74 | -- Signals for the value change detector. 75 | signal s_prev_stable : std_logic_vector(BITS-1 downto 0); 76 | signal s_stable_changed : std_logic; 77 | signal s_steady_cycles : unsigned(C_STEADY_CYCLES_BITS-1 downto 0); 78 | 79 | -- Intel/Altera specific constraints. 80 | attribute ALTERA_ATTRIBUTE : string; 81 | attribute ALTERA_ATTRIBUTE of rtl : architecture is "-name SDC_STATEMENT ""set_false_path -to [get_registers {*|synchronizer:*|s_metastable*}] """; 82 | attribute ALTERA_ATTRIBUTE of s_metastable : signal is "-name SYNCHRONIZER_IDENTIFICATION ""FORCED IF ASYNCHRONOUS"""; 83 | attribute PRESERVE : boolean; 84 | attribute PRESERVE of s_metastable : signal is true; 85 | attribute PRESERVE of s_stable : signal is true; 86 | 87 | -- Xilinx specific constraints. 88 | attribute ASYNC_REG : string; 89 | attribute ASYNC_REG of s_metastable : signal is "TRUE"; 90 | attribute SHREG_EXTRACT : string; 91 | attribute SHREG_EXTRACT of s_metastable : signal is "NO"; 92 | attribute SHREG_EXTRACT of s_stable : signal is "NO"; 93 | begin 94 | -- Synchronize the source signal using two flip-flops in series. 95 | process(i_rst, i_clk) 96 | begin 97 | if i_rst = '1' then 98 | s_metastable <= (others => '0'); 99 | s_stable <= (others => '0'); 100 | elsif rising_edge(i_clk) then 101 | s_metastable <= i_d; 102 | s_stable <= s_metastable; 103 | end if; 104 | end process; 105 | 106 | SteadyGen: if STEADY_CYCLES > 0 generate 107 | -- Only accept a value after STEADY_CYCLES cycles of steady state. 108 | s_stable_changed <= '1' when s_stable /= s_prev_stable else '0'; 109 | 110 | process(i_rst, i_clk) 111 | begin 112 | if i_rst = '1' then 113 | s_prev_stable <= (others => '0'); 114 | s_steady_cycles <= to_unsigned(0, C_STEADY_CYCLES_BITS); 115 | o_q <= (others => '0'); 116 | elsif rising_edge(i_clk) then 117 | -- Count the number of steady cycles that we have. 118 | if s_stable_changed = '1' then 119 | s_steady_cycles <= to_unsigned(0, C_STEADY_CYCLES_BITS); 120 | else 121 | s_steady_cycles <= s_steady_cycles + to_unsigned(1, C_STEADY_CYCLES_BITS); 122 | end if; 123 | 124 | -- Time to update the output value? 125 | if s_steady_cycles = to_unsigned(STEADY_CYCLES, C_STEADY_CYCLES_BITS) then 126 | o_q <= s_stable; 127 | end if; 128 | 129 | s_prev_stable <= s_stable; 130 | end if; 131 | end process; 132 | else generate 133 | o_q <= s_stable; 134 | end generate; 135 | end rtl; 136 | -------------------------------------------------------------------------------- /src/rtl/vid_palette.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | library ieee; 21 | use ieee.std_logic_1164.all; 22 | use ieee.numeric_std.all; 23 | 24 | ---------------------------------------------------------------------------------------------------- 25 | -- Video color palette memory. 26 | ---------------------------------------------------------------------------------------------------- 27 | 28 | entity vid_palette is 29 | port( 30 | i_rst : in std_logic; 31 | i_clk : in std_logic; 32 | 33 | i_write_enable : in std_logic; 34 | i_write_addr : in std_logic_vector(7 downto 0); 35 | i_write_data : in std_logic_vector(31 downto 0); 36 | 37 | i_read_addr : in std_logic_vector(7 downto 0); 38 | o_read_data : out std_logic_vector(31 downto 0) 39 | ); 40 | end vid_palette; 41 | 42 | architecture rtl of vid_palette is 43 | type T_MEM is array (255 downto 0) of std_logic_vector(31 downto 0); 44 | signal s_mem : T_MEM; 45 | begin 46 | -- The palette memory is a simple dual port memory (should synthesize to BRAM 47 | -- in an FPGA). 48 | process(i_clk) 49 | begin 50 | if rising_edge(i_clk) then 51 | if i_write_enable = '1' then 52 | s_mem(to_integer(unsigned(i_write_addr))) <= i_write_data; 53 | end if; 54 | o_read_data <= s_mem(to_integer(unsigned(i_read_addr))); 55 | end if; 56 | end process; 57 | end rtl; 58 | -------------------------------------------------------------------------------- /src/rtl/vid_pix_prefetch.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2020 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a pixel prefetch cache that aims to keep low priority pixel pipelines fed with data even 22 | -- during high priority pixel pipeline memory cycles. 23 | ---------------------------------------------------------------------------------------------------- 24 | 25 | library ieee; 26 | use ieee.std_logic_1164.all; 27 | use ieee.numeric_std.all; 28 | use work.vid_types.all; 29 | 30 | entity vid_pix_prefetch is 31 | port( 32 | i_rst : in std_logic; 33 | i_clk : in std_logic; 34 | 35 | -- Interface from the pixel pipeline. 36 | i_read_en : in std_logic; 37 | i_read_adr : in std_logic_vector(23 downto 0); 38 | i_decremental_read : in std_logic; 39 | i_row_start_imminent : in std_logic; 40 | i_row_start_addr : in std_logic_vector(23 downto 0); 41 | o_read_ack : out std_logic; 42 | o_read_dat : out std_logic_vector(31 downto 0); 43 | 44 | -- Interface to the RAM. 45 | o_read_en : out std_logic; 46 | o_read_adr : out std_logic_vector(23 downto 0); 47 | i_read_ack : in std_logic; 48 | i_read_dat : in std_logic_vector(31 downto 0) 49 | ); 50 | end vid_pix_prefetch; 51 | 52 | architecture rtl of vid_pix_prefetch is 53 | signal s_speculative_read_en : std_logic; 54 | signal s_speculative_expect_ack : std_logic; 55 | signal s_prev_read_en : std_logic; 56 | signal s_prefetch_adr : std_logic_vector(23 downto 0); 57 | signal s_cache_hit : std_logic; 58 | signal s_cached_adr : std_logic_vector(23 downto 0); 59 | signal s_cached_dat : std_logic_vector(31 downto 0); 60 | begin 61 | process(i_clk, i_rst) 62 | variable v_speculate : std_logic; 63 | begin 64 | if i_rst = '1' then 65 | s_speculative_read_en <= '0'; 66 | s_speculative_expect_ack <= '0'; 67 | s_prev_read_en <= '0'; 68 | s_prefetch_adr <= (others => '0'); 69 | s_cache_hit <= '0'; 70 | s_cached_adr <= (others => '1'); 71 | s_cached_dat <= (others => '0'); 72 | elsif rising_edge(i_clk) then 73 | -- Did we have a cache hit? 74 | if i_read_adr = s_cached_adr then 75 | s_cache_hit <= '1'; 76 | else 77 | s_cache_hit <= '0'; 78 | end if; 79 | 80 | -- Should we cache a speculative read? 81 | if i_read_ack = '1' and s_speculative_expect_ack = '1' then 82 | s_cached_adr <= s_prefetch_adr; 83 | s_cached_dat <= i_read_dat; 84 | v_speculate := '0'; 85 | else 86 | -- Continue an ongoing speculative read until we get an ack. 87 | v_speculate := s_speculative_read_en; 88 | end if; 89 | 90 | -- Start a new speculative read cycle? 91 | if i_read_en = '1' then 92 | -- Determine the next likey read address. 93 | if i_decremental_read = '1' then 94 | s_prefetch_adr <= std_logic_vector(unsigned(i_read_adr) - 1); 95 | else 96 | s_prefetch_adr <= std_logic_vector(unsigned(i_read_adr) + 1); 97 | end if; 98 | v_speculate := '1'; 99 | elsif i_row_start_imminent = '1' then 100 | -- Prefetch the first word of the row before the new row starts. 101 | s_prefetch_adr <= i_row_start_addr; 102 | v_speculate := '1'; 103 | end if; 104 | 105 | -- Did the pixel pipeline issue a read request during the last cycle? 106 | s_prev_read_en <= i_read_en; 107 | 108 | -- Do we expect an ack for a speculative read during the next cycle? 109 | s_speculative_expect_ack <= s_speculative_read_en; 110 | 111 | -- Can we start a speculative read during the next cycle? 112 | s_speculative_read_en <= v_speculate; 113 | end if; 114 | end process; 115 | 116 | -- Outputs to the memory subsystem. 117 | o_read_en <= i_read_en or s_speculative_read_en; 118 | o_read_adr <= i_read_adr when i_read_en = '1' else 119 | s_prefetch_adr; 120 | 121 | -- Outputs to the pixel pipeline. 122 | o_read_ack <= s_prev_read_en and (s_cache_hit or i_read_ack); 123 | o_read_dat <= s_cached_dat when s_cache_hit = '1' else 124 | i_read_dat; 125 | end rtl; 126 | -------------------------------------------------------------------------------- /src/rtl/vid_raster.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | library ieee; 21 | use ieee.std_logic_1164.all; 22 | use ieee.numeric_std.all; 23 | use work.vid_types.all; 24 | 25 | entity vid_raster is 26 | generic( 27 | VIDEO_CONFIG : T_VIDEO_CONFIG; 28 | 29 | X_COORD_BITS : positive := 12; -- Number of bits required for representing an x coordinate. 30 | Y_COORD_BITS : positive := 11 -- Number of bits required for representing a y coordinate. 31 | ); 32 | port( 33 | i_rst : in std_logic; 34 | i_clk : in std_logic; 35 | 36 | o_x_pos : out std_logic_vector(X_COORD_BITS-1 downto 0); 37 | o_y_pos : out std_logic_vector(Y_COORD_BITS-1 downto 0); 38 | 39 | o_hsync : out std_logic; 40 | o_vsync : out std_logic; 41 | o_restart_frame : out std_logic 42 | ); 43 | end vid_raster; 44 | 45 | architecture rtl of vid_raster is 46 | constant C_WIDTH : positive := VIDEO_CONFIG.width; 47 | constant C_HEIGHT : positive := VIDEO_CONFIG.height; 48 | constant C_FRONT_PORCH_H : positive := VIDEO_CONFIG.front_porch_h; 49 | constant C_SYNC_WIDTH_H : positive := VIDEO_CONFIG.sync_width_h; 50 | constant C_BACK_PORCH_H : positive := VIDEO_CONFIG.back_porch_h; 51 | constant C_FRONT_PORCH_V : positive := VIDEO_CONFIG.front_porch_v; 52 | constant C_SYNC_WIDTH_V : positive := VIDEO_CONFIG.sync_width_v; 53 | constant C_BACK_PORCH_V : positive := VIDEO_CONFIG.back_porch_v; 54 | constant C_POLARITY_H : std_logic := VIDEO_CONFIG.polarity_h; 55 | constant C_POLARITY_V : std_logic := VIDEO_CONFIG.polarity_v; 56 | 57 | -- ----+------------+-----------------------+-------------+------------+---- 58 | -- ... | Back porch | Video | Front porch | Sync pulse | ... 59 | -- ----+------------+-----------------------+-------------+------------+---- 60 | 61 | constant C_X_START : integer := -(C_FRONT_PORCH_H + C_SYNC_WIDTH_H + C_BACK_PORCH_H); 62 | constant C_X_SYNC_START : integer := -(C_SYNC_WIDTH_H + C_BACK_PORCH_H); 63 | constant C_X_SYNC_END : integer := -C_BACK_PORCH_H; 64 | constant C_X_END : integer := C_WIDTH; 65 | 66 | constant C_Y_START : integer := -(C_FRONT_PORCH_V + C_SYNC_WIDTH_V + C_BACK_PORCH_V); 67 | constant C_Y_SYNC_START : integer := -(C_SYNC_WIDTH_V + C_BACK_PORCH_V); 68 | constant C_Y_SYNC_END : integer := -C_BACK_PORCH_V; 69 | constant C_Y_END : integer := C_HEIGHT; 70 | 71 | signal s_x_pos_plus_1 : signed(X_COORD_BITS-1 downto 0); 72 | signal s_y_pos_plus_1 : signed(Y_COORD_BITS-1 downto 0); 73 | signal s_next_restart_line : std_logic; 74 | signal s_next_restart_frame : std_logic; 75 | signal s_next_x_pos : signed(X_COORD_BITS-1 downto 0); 76 | signal s_next_y_pos : signed(Y_COORD_BITS-1 downto 0); 77 | signal s_next_hsync : std_logic; 78 | signal s_next_vsync : std_logic; 79 | 80 | signal s_x_pos : signed(X_COORD_BITS-1 downto 0); 81 | signal s_y_pos : signed(Y_COORD_BITS-1 downto 0); 82 | signal s_hsync : std_logic; 83 | signal s_vsync : std_logic; 84 | signal s_restart_frame : std_logic; 85 | begin 86 | -- End of line and/or frame? 87 | s_next_restart_line <= '1' when s_x_pos = to_signed(C_X_END-1, X_COORD_BITS) else '0'; 88 | s_next_restart_frame <= s_next_restart_line when s_y_pos = to_signed(C_Y_END-1, Y_COORD_BITS) else '0'; 89 | 90 | -- Calculate the next x and y coordinates. 91 | s_x_pos_plus_1 <= s_x_pos + to_signed(1, X_COORD_BITS); 92 | s_y_pos_plus_1 <= s_y_pos + to_signed(1, Y_COORD_BITS); 93 | s_next_x_pos <= to_signed(C_X_START, X_COORD_BITS) when s_next_restart_line = '1' else s_x_pos_plus_1; 94 | s_next_y_pos <= to_signed(C_Y_START, Y_COORD_BITS) when s_next_restart_frame = '1' else 95 | s_y_pos_plus_1 when s_next_restart_line = '1' else 96 | s_y_pos; 97 | 98 | -- Are we within the horizontal and/or vertical sync periods? 99 | s_next_hsync <= C_POLARITY_H when s_x_pos >= to_signed(C_X_SYNC_START-1, X_COORD_BITS) and 100 | s_x_pos < to_signed(C_X_SYNC_END-1, X_COORD_BITS) else not C_POLARITY_H; 101 | s_next_vsync <= C_POLARITY_V when s_y_pos >= to_signed(C_Y_SYNC_START-1, Y_COORD_BITS) and 102 | s_y_pos < to_signed(C_Y_SYNC_END-1, Y_COORD_BITS) else not C_POLARITY_V; 103 | 104 | process(i_clk, i_rst) 105 | begin 106 | if i_rst = '1' then 107 | s_x_pos <= to_signed(C_X_START, X_COORD_BITS); 108 | s_y_pos <= to_signed(C_Y_START, Y_COORD_BITS); 109 | s_hsync <= not C_POLARITY_H; 110 | s_vsync <= not C_POLARITY_V; 111 | s_restart_frame <= '1'; 112 | elsif rising_edge(i_clk) then 113 | s_x_pos <= s_next_x_pos; 114 | s_y_pos <= s_next_y_pos; 115 | s_hsync <= s_next_hsync; 116 | s_vsync <= s_next_vsync; 117 | s_restart_frame <= s_next_restart_frame; 118 | end if; 119 | end process; 120 | 121 | -- Outputs. 122 | o_x_pos <= std_logic_vector(s_x_pos); 123 | o_y_pos <= std_logic_vector(s_y_pos); 124 | o_hsync <= s_hsync; 125 | o_vsync <= s_vsync; 126 | o_restart_frame <= s_restart_frame; 127 | end rtl; 128 | -------------------------------------------------------------------------------- /src/rtl/vid_regs.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | library ieee; 21 | use ieee.std_logic_1164.all; 22 | use work.vid_types.all; 23 | 24 | ---------------------------------------------------------------------------------------------------- 25 | -- Video control registers. 26 | ---------------------------------------------------------------------------------------------------- 27 | 28 | entity vid_regs is 29 | port( 30 | i_rst : in std_logic; 31 | i_clk : in std_logic; 32 | 33 | i_restart_frame : in std_logic; 34 | i_write_enable : in std_logic; 35 | i_write_addr : in std_logic_vector(2 downto 0); 36 | i_write_data : in std_logic_vector(23 downto 0); 37 | 38 | o_regs : out T_VID_REGS 39 | ); 40 | end vid_regs; 41 | 42 | architecture rtl of vid_regs is 43 | constant C_DEFAULT_ADDR : std_logic_vector(23 downto 0) := x"000000"; 44 | constant C_DEFAULT_XOFFS : std_logic_vector(23 downto 0) := x"000000"; 45 | constant C_DEFAULT_XINCR : std_logic_vector(23 downto 0) := x"004000"; 46 | constant C_DEFAULT_HSTRT : std_logic_vector(23 downto 0) := x"000000"; 47 | constant C_DEFAULT_HSTOP : std_logic_vector(23 downto 0) := x"000000"; 48 | constant C_DEFAULT_CMODE : std_logic_vector(23 downto 0) := x"000002"; 49 | constant C_DEFAULT_RMODE : std_logic_vector(23 downto 0) := x"000135"; 50 | 51 | signal s_regs : T_VID_REGS; 52 | signal s_next_regs : T_VID_REGS; 53 | begin 54 | -- Write logic. 55 | s_next_regs.ADDR <= i_write_data when i_write_enable = '1' and i_write_addr = "000" else 56 | C_DEFAULT_ADDR when i_restart_frame = '1' else 57 | s_regs.ADDR; 58 | s_next_regs.XOFFS <= i_write_data when i_write_enable = '1' and i_write_addr = "001" else 59 | C_DEFAULT_XOFFS when i_restart_frame = '1' else 60 | s_regs.XOFFS; 61 | s_next_regs.XINCR <= i_write_data when i_write_enable = '1' and i_write_addr = "010" else 62 | C_DEFAULT_XINCR when i_restart_frame = '1' else 63 | s_regs.XINCR; 64 | s_next_regs.HSTRT <= i_write_data when i_write_enable = '1' and i_write_addr = "011" else 65 | C_DEFAULT_HSTRT when i_restart_frame = '1' else 66 | s_regs.HSTRT; 67 | s_next_regs.HSTOP <= i_write_data when i_write_enable = '1' and i_write_addr = "100" else 68 | C_DEFAULT_HSTOP when i_restart_frame = '1' else 69 | s_regs.HSTOP; 70 | s_next_regs.CMODE <= i_write_data when i_write_enable = '1' and i_write_addr = "101" else 71 | C_DEFAULT_CMODE when i_restart_frame = '1' else 72 | s_regs.CMODE; 73 | s_next_regs.RMODE <= i_write_data when i_write_enable = '1' and i_write_addr = "110" else 74 | C_DEFAULT_RMODE when i_restart_frame = '1' else 75 | s_regs.RMODE; 76 | 77 | -- Clocked registers. 78 | process(i_clk, i_rst) 79 | begin 80 | if i_rst = '1' then 81 | s_regs.ADDR <= C_DEFAULT_ADDR; 82 | s_regs.XOFFS <= C_DEFAULT_XOFFS; 83 | s_regs.XINCR <= C_DEFAULT_XINCR; 84 | s_regs.HSTRT <= C_DEFAULT_HSTRT; 85 | s_regs.HSTOP <= C_DEFAULT_HSTOP; 86 | s_regs.CMODE <= C_DEFAULT_CMODE; 87 | s_regs.RMODE <= C_DEFAULT_RMODE; 88 | elsif rising_edge(i_clk) then 89 | s_regs <= s_next_regs; 90 | end if; 91 | end process; 92 | 93 | -- Outputs. 94 | o_regs <= s_regs; 95 | end rtl; 96 | -------------------------------------------------------------------------------- /src/rtl/vid_types.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This file contains common types for the video logic. 22 | ---------------------------------------------------------------------------------------------------- 23 | 24 | library ieee; 25 | use ieee.std_logic_1164.all; 26 | 27 | package vid_types is 28 | -------------------------------------------------------------------------------------------------- 29 | -- Video control registers. 30 | -------------------------------------------------------------------------------------------------- 31 | type T_VID_REGS is record 32 | ADDR : std_logic_vector(23 downto 0); 33 | XOFFS : std_logic_vector(23 downto 0); 34 | XINCR : std_logic_vector(23 downto 0); 35 | HSTRT : std_logic_vector(23 downto 0); 36 | HSTOP : std_logic_vector(23 downto 0); 37 | CMODE : std_logic_vector(23 downto 0); 38 | RMODE : std_logic_vector(23 downto 0); 39 | end record T_VID_REGS; 40 | 41 | 42 | ------------------------------------------------------------------------------------------------ 43 | -- Supported video resolution configurations. 44 | ------------------------------------------------------------------------------------------------ 45 | type T_VIDEO_CONFIG is record 46 | width : positive; 47 | height : positive; 48 | front_porch_h : positive; 49 | sync_width_h : positive; 50 | back_porch_h : positive; 51 | front_porch_v : positive; 52 | sync_width_v : positive; 53 | back_porch_v : positive; 54 | polarity_h : std_logic; 55 | polarity_v : std_logic; 56 | end record T_VIDEO_CONFIG; 57 | 58 | -- 1920 x 1080 @ 60 Hz, pixel clock = 148.5 MHz 59 | constant C_1920_1080 : T_VIDEO_CONFIG := ( 60 | width => 1920, 61 | height => 1080, 62 | front_porch_h => 88, 63 | sync_width_h => 44, 64 | back_porch_h => 148, 65 | front_porch_v => 4, 66 | sync_width_v => 5, 67 | back_porch_v => 36, 68 | polarity_h => '1', 69 | polarity_v => '1' 70 | ); 71 | 72 | -- 1280 x 720 @ 60 Hz, pixel clock = 74.25 MHz 73 | constant C_1280_720 : T_VIDEO_CONFIG := ( 74 | width => 1280, 75 | height => 720, 76 | front_porch_h => 110, 77 | sync_width_h => 40, 78 | back_porch_h => 220, 79 | front_porch_v => 5, 80 | sync_width_v => 5, 81 | back_porch_v => 20, 82 | polarity_h => '1', 83 | polarity_v => '1' 84 | ); 85 | 86 | -- 800 x 600 @ 60 Hz, pixel clock = 40.0 MHz 87 | constant C_800_600 : T_VIDEO_CONFIG := ( 88 | width => 800, 89 | height => 600, 90 | front_porch_h => 40, 91 | sync_width_h => 128, 92 | back_porch_h => 88, 93 | front_porch_v => 1, 94 | sync_width_v => 4, 95 | back_porch_v => 23, 96 | polarity_h => '1', 97 | polarity_v => '1' 98 | ); 99 | 100 | -- 640 x 480 @ 60 Hz, pixel clock = 25.175 MHz 101 | constant C_640_480 : T_VIDEO_CONFIG := ( 102 | width => 640, 103 | height => 480, 104 | front_porch_h => 16, 105 | sync_width_h => 96, 106 | back_porch_h => 48, 107 | front_porch_v => 10, 108 | sync_width_v => 2, 109 | back_porch_v => 33, 110 | polarity_h => '0', 111 | polarity_v => '0' 112 | ); 113 | end package; 114 | -------------------------------------------------------------------------------- /src/rtl/vid_vcpp_stack.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | library ieee; 21 | use ieee.std_logic_1164.all; 22 | use ieee.numeric_std.all; 23 | 24 | ---------------------------------------------------------------------------------------------------- 25 | -- VCPP call stack. 26 | ---------------------------------------------------------------------------------------------------- 27 | 28 | entity vid_vcpp_stack is 29 | generic( 30 | LOG2_NUM_ENTRIES : positive := 4; 31 | NUM_DATA_BITS : positive := 24 32 | ); 33 | port( 34 | i_rst : in std_logic; 35 | i_clk : in std_logic; 36 | 37 | i_push : in std_logic; 38 | i_pop : in std_logic; 39 | i_data : in std_logic_vector(NUM_DATA_BITS-1 downto 0); 40 | o_data : out std_logic_vector(NUM_DATA_BITS-1 downto 0) 41 | ); 42 | end vid_vcpp_stack; 43 | 44 | architecture rtl of vid_vcpp_stack is 45 | constant C_NUM_ENTIRES : positive := 2**LOG2_NUM_ENTRIES; 46 | type T_STACK is array (C_NUM_ENTIRES-1 downto 0) of std_logic_vector(NUM_DATA_BITS-1 downto 0); 47 | signal s_stack : T_STACK; 48 | signal s_pos : unsigned(LOG2_NUM_ENTRIES-1 downto 0); 49 | 50 | -- This RAM is tiny (only 384 bits), so use logic cells instead of block RAM so that we don't 51 | -- waste a full BRAM block. 52 | attribute RAMSTYLE : string; 53 | attribute RAMSTYLE of s_stack : signal is "MLAB"; 54 | begin 55 | process(i_clk, i_rst) 56 | variable v_pos : unsigned(LOG2_NUM_ENTRIES-1 downto 0); 57 | variable v_next_pos : unsigned(LOG2_NUM_ENTRIES-1 downto 0); 58 | begin 59 | if i_rst = '1' then 60 | s_pos <= to_unsigned(0, LOG2_NUM_ENTRIES); 61 | elsif rising_edge(i_clk) then 62 | v_pos := s_pos; 63 | 64 | if i_push = '1' then 65 | v_next_pos := v_pos - 1; 66 | s_stack(to_integer(v_next_pos)) <= i_data; 67 | elsif i_pop = '1' then 68 | v_next_pos := v_pos + 1; 69 | else 70 | v_next_pos := v_pos; 71 | end if; 72 | 73 | o_data <= s_stack(to_integer(v_pos)); 74 | 75 | s_pos <= v_next_pos; 76 | end if; 77 | end process; 78 | end rtl; 79 | -------------------------------------------------------------------------------- /src/rtl/vram.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is a dual-ported RAM module that implements the internal video RAM. It has the following 22 | -- properties: 23 | -- * Configurable size (2^N words). 24 | -- * 32-bit data width. 25 | -- * Port A: 26 | -- - Wishbone B4 pipelined interface. 27 | -- - Byte enable / select for write operations. 28 | -- - Single cycle read/write operation. 29 | -- * Port B: 30 | -- - Read-only (no byte enable) 31 | -- * Synthesizes to BRAM 32 | ---------------------------------------------------------------------------------------------------- 33 | 34 | library ieee; 35 | use ieee.std_logic_1164.all; 36 | use ieee.numeric_std.all; 37 | 38 | entity vram is 39 | generic( 40 | ADR_BITS : positive := 10 -- 2**10 = 1024 words 41 | ); 42 | port( 43 | -- Reset signal. 44 | i_rst : in std_logic; 45 | 46 | -- Wishbone memory interface (b4 pipelined slave). 47 | -- See: https://cdn.opencores.org/downloads/wbspec_b4.pdf 48 | i_wb_clk : in std_logic; 49 | i_wb_cyc : in std_logic; 50 | i_wb_stb : in std_logic; 51 | i_wb_adr : in std_logic_vector(ADR_BITS-1 downto 0); 52 | i_wb_dat : in std_logic_vector(31 downto 0); 53 | i_wb_we : in std_logic; 54 | i_wb_sel : in std_logic_vector(32/8-1 downto 0); 55 | o_wb_dat : out std_logic_vector(31 downto 0); 56 | o_wb_ack : out std_logic; 57 | o_wb_stall : out std_logic; 58 | 59 | -- Read-only second port to the RAM. 60 | i_read_clk : in std_logic; 61 | i_read_adr : in std_logic_vector(ADR_BITS-1 downto 0); 62 | o_read_dat : out std_logic_vector(31 downto 0) 63 | ); 64 | end vram; 65 | 66 | architecture rtl of vram is 67 | signal s_is_valid_wb_request : std_logic; 68 | signal s_we_a : std_logic; 69 | signal s_we_a_0 : std_logic; 70 | signal s_we_a_1 : std_logic; 71 | signal s_we_a_2 : std_logic; 72 | signal s_we_a_3 : std_logic; 73 | begin 74 | -- Wishbone control logic. 75 | s_is_valid_wb_request <= i_wb_cyc and i_wb_stb; 76 | s_we_a <= s_is_valid_wb_request and i_wb_we; 77 | s_we_a_0 <= s_we_a and i_wb_sel(0); 78 | s_we_a_1 <= s_we_a and i_wb_sel(1); 79 | s_we_a_2 <= s_we_a and i_wb_sel(2); 80 | s_we_a_3 <= s_we_a and i_wb_sel(3); 81 | 82 | -- We always ack and never stall - we're that fast ;-) 83 | process(i_wb_clk) 84 | begin 85 | if rising_edge(i_wb_clk) then 86 | o_wb_ack <= s_is_valid_wb_request; 87 | end if; 88 | end process; 89 | o_wb_stall <= '0'; 90 | 91 | -- We instatiate four 8-bit wide RAM entities in order to support byte select. 92 | ram_tdp_0: entity work.ram_true_dual_port 93 | generic map ( 94 | DATA_BITS => 8, 95 | ADR_BITS => ADR_BITS 96 | ) 97 | port map ( 98 | i_clk_a => i_wb_clk, 99 | i_we_a => s_we_a_0, 100 | i_adr_a => i_wb_adr, 101 | i_data_a => i_wb_dat(7 downto 0), 102 | o_data_a => o_wb_dat(7 downto 0), 103 | 104 | i_clk_b => i_read_clk, 105 | i_adr_b => i_read_adr, 106 | o_data_b => o_read_dat(7 downto 0) 107 | ); 108 | 109 | ram_tdp_1: entity work.ram_true_dual_port 110 | generic map ( 111 | DATA_BITS => 8, 112 | ADR_BITS => ADR_BITS 113 | ) 114 | port map ( 115 | i_clk_a => i_wb_clk, 116 | i_we_a => s_we_a_1, 117 | i_adr_a => i_wb_adr, 118 | i_data_a => i_wb_dat(15 downto 8), 119 | o_data_a => o_wb_dat(15 downto 8), 120 | 121 | i_clk_b => i_read_clk, 122 | i_adr_b => i_read_adr, 123 | o_data_b => o_read_dat(15 downto 8) 124 | ); 125 | 126 | ram_tdp_2: entity work.ram_true_dual_port 127 | generic map ( 128 | DATA_BITS => 8, 129 | ADR_BITS => ADR_BITS 130 | ) 131 | port map ( 132 | i_clk_a => i_wb_clk, 133 | i_we_a => s_we_a_2, 134 | i_adr_a => i_wb_adr, 135 | i_data_a => i_wb_dat(23 downto 16), 136 | o_data_a => o_wb_dat(23 downto 16), 137 | 138 | i_clk_b => i_read_clk, 139 | i_adr_b => i_read_adr, 140 | o_data_b => o_read_dat(23 downto 16) 141 | ); 142 | 143 | ram_tdp_3: entity work.ram_true_dual_port 144 | generic map ( 145 | DATA_BITS => 8, 146 | ADR_BITS => ADR_BITS 147 | ) 148 | port map ( 149 | i_clk_a => i_wb_clk, 150 | i_we_a => s_we_a_3, 151 | i_adr_a => i_wb_adr, 152 | i_data_a => i_wb_dat(31 downto 24), 153 | o_data_a => o_wb_dat(31 downto 24), 154 | 155 | i_clk_b => i_read_clk, 156 | i_adr_b => i_read_adr, 157 | o_data_b => o_read_dat(31 downto 24) 158 | ); 159 | end rtl; 160 | -------------------------------------------------------------------------------- /src/rtl/xram_sdram.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2020 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | ---------------------------------------------------------------------------------------------------- 21 | -- This is an XRAM implementation for SDRAM memories. 22 | -- 23 | -- TODO(m): Add a simple caching mechanism, and transfer more than 32 bits per request. 24 | ---------------------------------------------------------------------------------------------------- 25 | 26 | library ieee; 27 | use ieee.std_logic_1164.all; 28 | use ieee.numeric_std.all; 29 | use ieee.math_real.all; 30 | 31 | entity xram_sdram is 32 | generic ( 33 | -- Clock frequency (in Hz) 34 | CPU_CLK_HZ : integer; 35 | 36 | -- See sdram.vhd for the details of these generics. 37 | SDRAM_ADDR_WIDTH : integer := 13; 38 | SDRAM_DATA_WIDTH : integer := 16; 39 | SDRAM_COL_WIDTH : integer := 9; 40 | SDRAM_ROW_WIDTH : integer := 13; 41 | SDRAM_BANK_WIDTH : integer := 2; 42 | CAS_LATENCY : integer := 2; 43 | T_DESL : real := 200_000.0; 44 | T_MRD : real := 12.0; 45 | T_RC : real := 60.0; 46 | T_RCD : real := 18.0; 47 | T_RP : real := 18.0; 48 | T_DPL : real := 14.0; 49 | T_REF : real := 64_000_000.0; 50 | 51 | -- FIFO configuration. 52 | FIFO_DEPTH : integer := 16 53 | ); 54 | port ( 55 | -- Reset signal. 56 | i_rst : in std_logic; 57 | 58 | -- Wishbone memory interface (b4 pipelined slave). 59 | -- See: https://cdn.opencores.org/downloads/wbspec_b4.pdf 60 | i_wb_clk : in std_logic; 61 | i_wb_cyc : in std_logic; 62 | i_wb_stb : in std_logic; 63 | i_wb_adr : in std_logic_vector(29 downto 0); 64 | i_wb_dat : in std_logic_vector(31 downto 0); 65 | i_wb_we : in std_logic; 66 | i_wb_sel : in std_logic_vector(32/8-1 downto 0); 67 | o_wb_dat : out std_logic_vector(31 downto 0); 68 | o_wb_ack : out std_logic; 69 | o_wb_stall : out std_logic; 70 | o_wb_err : out std_logic; 71 | 72 | -- SDRAM interface. 73 | o_sdram_a : out std_logic_vector(SDRAM_ADDR_WIDTH-1 downto 0); 74 | o_sdram_ba : out std_logic_vector(SDRAM_BANK_WIDTH-1 downto 0); 75 | io_sdram_dq : inout std_logic_vector(SDRAM_DATA_WIDTH-1 downto 0); 76 | o_sdram_cke : out std_logic; 77 | o_sdram_cs_n : out std_logic; 78 | o_sdram_ras_n : out std_logic; 79 | o_sdram_cas_n : out std_logic; 80 | o_sdram_we_n : out std_logic; 81 | o_sdram_dqm : out std_logic_vector(SDRAM_DATA_WIDTH/8-1 downto 0) 82 | ); 83 | end xram_sdram; 84 | 85 | architecture rtl of xram_sdram is 86 | constant C_ADDR_WIDTH : integer := SDRAM_COL_WIDTH+SDRAM_ROW_WIDTH+SDRAM_BANK_WIDTH-1; 87 | constant C_DATA_WIDTH : integer := i_wb_dat'length; 88 | constant C_SEL_WIDTH : integer := C_DATA_WIDTH/8; 89 | 90 | constant C_MEM_OP_WIDTH : integer := C_ADDR_WIDTH + C_DATA_WIDTH + C_SEL_WIDTH + 1; 91 | 92 | -- Input signals. 93 | signal s_adr : std_logic_vector(C_ADDR_WIDTH-1 downto 0); 94 | signal s_dat_w : std_logic_vector(C_DATA_WIDTH-1 downto 0); 95 | signal s_we : std_logic; 96 | signal s_sel : std_logic_vector(C_SEL_WIDTH-1 downto 0); 97 | signal s_req : std_logic; 98 | 99 | -- FIFO signals. 100 | signal s_fifo_wr_en : std_logic; 101 | signal s_fifo_wr_data : std_logic_vector(C_MEM_OP_WIDTH-1 downto 0); 102 | signal s_fifo_full : std_logic; 103 | signal s_fifo_rd_en : std_logic; 104 | signal s_fifo_rd_data : std_logic_vector(C_MEM_OP_WIDTH-1 downto 0); 105 | signal s_fifo_empty : std_logic; 106 | 107 | -- Map of FIFO outputs. 108 | alias a_fifo_rd_adr : std_logic_vector(C_ADDR_WIDTH-1 downto 0) is s_fifo_rd_data(C_ADDR_WIDTH+C_DATA_WIDTH+C_SEL_WIDTH+1-1 downto C_DATA_WIDTH+C_SEL_WIDTH+1); 109 | alias a_fifo_rd_dat : std_logic_vector(C_DATA_WIDTH-1 downto 0) is s_fifo_rd_data(C_DATA_WIDTH+C_SEL_WIDTH+1-1 downto C_SEL_WIDTH+1); 110 | alias a_fifo_rd_sel : std_logic_vector(C_SEL_WIDTH-1 downto 0) is s_fifo_rd_data(C_SEL_WIDTH+1-1 downto 1); 111 | alias a_fifo_rd_we : std_logic is s_fifo_rd_data(0); 112 | 113 | -- Result signals. 114 | signal s_busy : std_logic; 115 | signal s_ack : std_logic; 116 | signal s_dat : std_logic_vector(C_DATA_WIDTH-1 downto 0); 117 | begin 118 | -- Instantiate the memory operation FIFO. 119 | fifo_1: entity work.fifo 120 | generic map ( 121 | G_WIDTH => C_MEM_OP_WIDTH, 122 | G_DEPTH => FIFO_DEPTH 123 | ) 124 | port map ( 125 | i_rst => i_rst, 126 | i_clk => i_wb_clk, 127 | i_wr_en => s_fifo_wr_en, 128 | i_wr_data => s_fifo_wr_data, 129 | o_full => s_fifo_full, 130 | i_rd_en => s_fifo_rd_en, 131 | o_rd_data => s_fifo_rd_data, 132 | o_empty => s_fifo_empty 133 | ); 134 | 135 | -- Write to the FIFO. 136 | s_fifo_wr_en <= i_wb_cyc and i_wb_stb and not s_fifo_full; 137 | s_fifo_wr_data <= i_wb_adr(C_ADDR_WIDTH-1 downto 0) & 138 | i_wb_dat & 139 | i_wb_sel & 140 | i_wb_we; 141 | 142 | -- Read from the FIFO and send to the SDRAM controller. 143 | s_fifo_rd_en <= (not s_busy) and (not s_fifo_empty); 144 | s_req <= (not s_busy) and (not s_fifo_empty); 145 | s_adr <= a_fifo_rd_adr; 146 | s_dat_w <= a_fifo_rd_dat; 147 | s_sel <= a_fifo_rd_sel; 148 | s_we <= a_fifo_rd_we; 149 | 150 | -- Instantiate the SDRAM controller. 151 | sdram_controller_1: entity work.sdram 152 | generic map ( 153 | G_CLK_FREQ_HZ => CPU_CLK_HZ, 154 | G_ADDR_WIDTH => C_ADDR_WIDTH, 155 | G_DATA_WIDTH => C_DATA_WIDTH, 156 | G_SDRAM_A_WIDTH => SDRAM_ADDR_WIDTH, 157 | G_SDRAM_DQ_WIDTH => SDRAM_DATA_WIDTH, 158 | G_SDRAM_BA_WIDTH => SDRAM_BANK_WIDTH, 159 | G_SDRAM_COL_WIDTH => SDRAM_COL_WIDTH, 160 | G_SDRAM_ROW_WIDTH => SDRAM_ROW_WIDTH, 161 | G_CAS_LATENCY => CAS_LATENCY, 162 | G_T_DESL => T_DESL, 163 | G_T_MRD => T_MRD, 164 | G_T_RC => T_RC, 165 | G_T_RCD => T_RCD, 166 | G_T_RP => T_RP, 167 | G_T_DPL => T_DPL, 168 | G_T_REF => T_REF 169 | ) 170 | port map ( 171 | i_rst => i_rst, 172 | i_clk => i_wb_clk, 173 | 174 | -- CPU/Wishbone interface. 175 | i_adr => s_adr, 176 | i_dat_w => s_dat_w, 177 | i_we => s_we, 178 | i_sel => s_sel, 179 | i_req => s_req, 180 | o_busy => s_busy, 181 | o_ack => s_ack, 182 | o_dat => s_dat, 183 | 184 | -- External SDRAM interface. 185 | o_sdram_a => o_sdram_a, 186 | o_sdram_ba => o_sdram_ba, 187 | io_sdram_dq => io_sdram_dq, 188 | o_sdram_cke => o_sdram_cke, 189 | o_sdram_cs_n => o_sdram_cs_n, 190 | o_sdram_ras_n => o_sdram_ras_n, 191 | o_sdram_cas_n => o_sdram_cas_n, 192 | o_sdram_we_n => o_sdram_we_n, 193 | o_sdram_dqm => o_sdram_dqm 194 | ); 195 | 196 | -- Wishbone outputs. 197 | o_wb_stall <= s_fifo_full; 198 | o_wb_ack <= s_ack; 199 | o_wb_dat <= s_dat; 200 | o_wb_err <= '0'; 201 | end rtl; 202 | 203 | -------------------------------------------------------------------------------- /src/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import struct 4 | import sys 5 | from vunit import VUnit 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], 'mc1-sdk/tools')) 8 | import vcpas 9 | 10 | 11 | _VIDEO_TB_VCP_SOURCE = "test/test-image-640x360-pal8.vcp" 12 | _VIDEO_TB_VRAM_FILE = "vunit_out/video_tb_ram.bin" 13 | 14 | 15 | def bake_video_tb_vram(): 16 | # Assemble the VCP. 17 | vcpas.assemble(_VIDEO_TB_VCP_SOURCE, _VIDEO_TB_VRAM_FILE, "bin") 18 | 19 | 20 | def main(): 21 | # Create VUnit instance by parsing command line arguments 22 | vu = VUnit.from_argv() 23 | 24 | # Create library 'lib' containing all the test benches... 25 | lib = vu.add_library("lib") 26 | lib.add_source_files("test/*_tb.vhd") 27 | 28 | # Add simulation models. 29 | lib.add_source_files("test/sdram_model.vhd") 30 | 31 | # Add the MC1 design. 32 | lib.add_source_files("rtl/bit_synchronizer.vhd") 33 | lib.add_source_files("rtl/dither.vhd") 34 | lib.add_source_files("rtl/mc1.vhd") 35 | lib.add_source_files("rtl/mmio_types.vhd") 36 | lib.add_source_files("rtl/mmio.vhd") 37 | lib.add_source_files("rtl/prng.vhd") 38 | lib.add_source_files("rtl/ps2_keyboard.vhd") 39 | lib.add_source_files("rtl/ps2_receiver.vhd") 40 | lib.add_source_files("rtl/ram_true_dual_port.vhd") 41 | lib.add_source_files("rtl/reset_conditioner.vhd") 42 | lib.add_source_files("rtl/reset_stabilizer.vhd") 43 | lib.add_source_files("rtl/sdram.vhd") 44 | lib.add_source_files("rtl/synchronizer.vhd") 45 | lib.add_source_files("rtl/vid_blend.vhd") 46 | lib.add_source_files("rtl/video_layer.vhd") 47 | lib.add_source_files("rtl/video.vhd") 48 | lib.add_source_files("rtl/vid_palette.vhd") 49 | lib.add_source_files("rtl/vid_pixel.vhd") 50 | lib.add_source_files("rtl/vid_pix_prefetch.vhd") 51 | lib.add_source_files("rtl/vid_raster.vhd") 52 | lib.add_source_files("rtl/vid_regs.vhd") 53 | lib.add_source_files("rtl/vid_types.vhd") 54 | lib.add_source_files("rtl/vid_vcpp_stack.vhd") 55 | lib.add_source_files("rtl/vid_vcpp.vhd") 56 | lib.add_source_files("rtl/vram.vhd") 57 | lib.add_source_files("rtl/wb_crossbar_2x4.vhd") 58 | lib.add_source_files("rtl/xram_sdram.vhd") 59 | 60 | # Add the MC1 boot ROM (must be generated with "make"). 61 | lib.add_source_files("rom/out/rom.vhd") 62 | 63 | # Add the MRISC32-A1 implementation. 64 | mrisc32 = vu.add_library("mrisc32") 65 | mrisc32.add_source_files("mrisc32-a1/rtl/agu/*.vhd") 66 | mrisc32.add_source_files("mrisc32-a1/rtl/alu/*.vhd") 67 | mrisc32.add_source_files("mrisc32-a1/rtl/common/*.vhd") 68 | mrisc32.add_source_files("mrisc32-a1/rtl/core/*.vhd") 69 | mrisc32.add_source_files("mrisc32-a1/rtl/fpu/*.vhd") 70 | mrisc32.add_source_files("mrisc32-a1/rtl/muldiv/*.vhd") 71 | mrisc32.add_source_files("mrisc32-a1/rtl/pipeline/*.vhd") 72 | mrisc32.add_source_files("mrisc32-a1/rtl/sau/*.vhd") 73 | 74 | # Bake the video_tb test data. 75 | bake_video_tb_vram() 76 | 77 | # Run vunit function 78 | vu.main() 79 | 80 | 81 | if __name__ == '__main__': 82 | main() 83 | -------------------------------------------------------------------------------- /src/test/dual-gradients.vcp: -------------------------------------------------------------------------------- 1 | ; -*- mode: vcpasm; tab-width: 4; indent-tabs-mode: nil; -*- 2 | ;----------------------------------------------------------------------------- 3 | ; This is a test program for video_tb. 4 | ;----------------------------------------------------------------------------- 5 | 6 | .include "mc1-defines.vcp" 7 | 8 | ; Set the program start address 9 | .org 0x000004 10 | 11 | layer1_start: 12 | jmp main 13 | nop 14 | nop 15 | nop 16 | 17 | layer2_start: 18 | setpal 0, 1 19 | .word 0x00000000 20 | waity 32767 21 | nop 22 | 23 | main: 24 | ; Set the video mode 25 | setreg XOFFS, 0x000000 26 | setreg CMODE, CM_PAL1 27 | setreg RMODE, RM_DITHER_WHITE 28 | setreg ADDR, image_data 29 | 30 | setpal 1, 1 31 | .word 0xffa0a080 32 | 33 | ; Activate video output starting at row 0. 34 | waity 0 35 | setreg HSTOP, 1280 36 | 37 | ; Generate video addresses for all rows. 38 | .set row, 0 39 | .set col, 280 40 | .set xinc, 0x000000 41 | .set xoff, 0x800000 42 | .set color1, 0xFF000000 43 | .set color2, 0xFFF000FF 44 | .rept 720 45 | waity row 46 | setreg XOFFS, xoff 47 | setreg XINCR, xinc 48 | setpal 0, 1 49 | .word color1 50 | waitx col 51 | setpal 0, 1 52 | .word color2 53 | .set row, row + 1 54 | .set col, col + 1 55 | .set xinc, xinc + 0x000010 56 | .set xoff, xoff - 0x002800 57 | .set color1, color1 + 0x00000101 58 | .set color2, color2 + 0x00000100 59 | .endr 60 | 61 | ; End of program 62 | waity 32767 63 | 64 | image_data: 65 | .word 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA 66 | .word 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA 67 | .word 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA 68 | .word 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA 69 | 70 | -------------------------------------------------------------------------------- /src/test/dummy_tb.vhd: -------------------------------------------------------------------------------- 1 | library vunit_lib; 2 | context vunit_lib.vunit_context; 3 | 4 | entity dummy_tb is 5 | generic (runner_cfg : string); 6 | end entity; 7 | 8 | architecture tb of dummy_tb is 9 | begin 10 | main : process 11 | begin 12 | test_runner_setup(runner, runner_cfg); 13 | 14 | report "Hello world!"; 15 | 16 | test_runner_cleanup(runner); 17 | end process; 18 | end architecture; -------------------------------------------------------------------------------- /src/test/mc1-defines.vcp: -------------------------------------------------------------------------------- 1 | ; -*- mode: vcpasm; tab-width: 4; indent-tabs-mode: nil; -*- 2 | ;----------------------------------------------------------------------------- 3 | ; This is a test program for video_tb. 4 | ;----------------------------------------------------------------------------- 5 | 6 | ; Video registers 7 | .set ADDR, 0 8 | .set XOFFS, 1 9 | .set XINCR, 2 10 | .set HSTRT, 3 11 | .set HSTOP, 4 12 | .set CMODE, 5 13 | .set RMODE, 6 14 | 15 | ; CMODE constants 16 | .set CM_RGBA8888, 0 17 | .set CM_RGBA5551, 1 18 | .set CM_PAL8, 2 19 | .set CM_PAL4, 3 20 | .set CM_PAL2, 4 21 | .set CM_PAL1, 5 22 | 23 | ; RMODE constants 24 | .set RM_DITHER_NONE, 0 25 | .set RM_DITHER_WHITE, 1 26 | 27 | ; Native video resolution. 28 | .set NATIVE_WIDTH, 1920 29 | .set NATIVE_HEIGHT, 1080 30 | 31 | -------------------------------------------------------------------------------- /src/test/pal24to32.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import struct 5 | 6 | 7 | def convert(infile, outfile): 8 | # Read the input file. 9 | with open(infile, "rb") as f: 10 | data = f.read() 11 | colors = [] 12 | for k in range(0, len(data), 3): 13 | # Convert to ABGR32 (RGBA8888) 14 | colors.append(0xff000000 | (data[k + 2] << 16) | (data[k + 1] << 8) | data[k]) 15 | 16 | print(f"Number of colors: {len(colors)}") 17 | 18 | # Write the output file. 19 | with open(outfile, "wb") as f: 20 | for color in colors: 21 | f.write(struct.pack(") of integer; 53 | 54 | -- Memory arrays. 55 | constant C_NUM_BANKS : integer := 2**BANK_WIDTH; 56 | constant C_NUM_ROWS : integer := 2**ROW_WIDTH; 57 | constant C_NUM_COLS : integer := 2**COL_WIDTH; 58 | 59 | type T_MEM_COLS is array (0 to C_NUM_COLS-1) of std_logic_vector(DATA_WIDTH-1 downto 0); 60 | type T_MEM_ROWS is array (0 to C_NUM_ROWS-1) of T_MEM_COLS; 61 | type T_MEM_BANKS is array (0 to C_NUM_BANKS-1) of T_MEM_ROWS; 62 | 63 | -- SDRAM commands (CS_ & RAS_ & CAS_ & WE_). 64 | subtype T_CMD is std_logic_vector(3 downto 0); 65 | constant C_CMD_MRS : T_CMD := "0000"; -- MODE REGISTER SET 66 | constant C_CMD_REF : T_CMD := "0001"; -- AUTO REFRESH 67 | constant C_CMD_PRE : T_CMD := "0010"; -- PRECHARGE 68 | constant C_CMD_ACT : T_CMD := "0011"; -- ROW/BANK ACTIVE 69 | constant C_CMD_WR : T_CMD := "0100"; -- WRITE BANK 70 | constant C_CMD_RD : T_CMD := "0101"; -- READ BANK 71 | constant C_CMD_NOP : T_CMD := "0111"; -- NOP 72 | 73 | -- Mode register. 74 | type T_MODE_REG is record 75 | cas_latency : integer; 76 | burst_type : integer; 77 | burst_length : integer; 78 | end record T_MODE_REG; 79 | 80 | function decode_mode_reg(addr : unsigned) return T_MODE_REG is 81 | variable v_mode_reg : T_MODE_REG; 82 | begin 83 | v_mode_reg.cas_latency := to_integer(addr(6 downto 4)); 84 | v_mode_reg.burst_type := to_integer(addr(3 downto 3)); 85 | v_mode_reg.burst_length := 2**to_integer(addr(2 downto 0)); 86 | return v_mode_reg; 87 | end function; 88 | 89 | -- Burst command queue. 90 | type T_BURST_CMD is (NONE, RD, WR); 91 | type T_BURST_QUEUE_ITEM is record 92 | cmd : T_BURST_CMD; 93 | bank : integer; 94 | row : integer; 95 | col : integer; 96 | end record T_BURST_QUEUE_ITEM; 97 | constant C_BURST_QUEUE_LEN : integer := 256; 98 | type T_BURST_QUEUE is array (0 to C_BURST_QUEUE_LEN-1) of T_BURST_QUEUE_ITEM; 99 | 100 | function decode_col(addr : std_logic_vector) return integer is 101 | variable v_col_addr : std_logic_vector(COL_WIDTH-1 downto 0); 102 | begin 103 | if COL_WIDTH <= 10 then 104 | v_col_addr := addr(COL_WIDTH-1 downto 0); 105 | else 106 | v_col_addr := addr(COL_WIDTH downto 11) & addr(9 downto 0); 107 | end if; 108 | return to_integer(unsigned(v_col_addr)); 109 | end function; 110 | 111 | function combine_with_mask(dold : std_logic_vector; dnew : std_logic_vector; dqm : std_logic_vector) return std_logic_vector is 112 | variable v_result : std_logic_vector(DATA_WIDTH-1 downto 0); 113 | variable v_hi : integer; 114 | variable v_lo : integer; 115 | begin 116 | for i in 0 to C_DQM_WIDTH-1 loop 117 | v_lo := i*8; 118 | v_hi := v_lo + 7; 119 | -- Note: DQM is active low. 120 | if dqm(i) = '0' then 121 | v_result(v_hi downto v_lo) := dnew(v_hi downto v_lo); 122 | else 123 | v_result(v_hi downto v_lo) := dold(v_hi downto v_lo); 124 | end if; 125 | end loop; 126 | return v_result; 127 | end function; 128 | 129 | signal s_mode_reg : T_MODE_REG; 130 | signal s_row_of_bank : T_INT_ARRAY(0 to C_NUM_BANKS-1); 131 | signal s_burst_idx : integer; 132 | begin 133 | -- Simple simulation of the behaviour of an SDRAM (just respond with some 134 | -- data for read requests). 135 | process(i_rst, i_clk) 136 | variable v_cmd : T_CMD; 137 | variable v_bank_no : integer; 138 | variable v_row_no : integer; 139 | variable v_col_no : integer; 140 | 141 | variable v_mem : T_MEM_BANKS; 142 | variable v_mem_data : std_logic_vector(DATA_WIDTH-1 downto 0); 143 | 144 | variable v_burst_queue : T_BURST_QUEUE; 145 | variable v_idx : integer; 146 | begin 147 | if i_rst = '1' then 148 | s_mode_reg <= ( 149 | cas_latency => 2, 150 | burst_type => 0, 151 | burst_length => 2 152 | ); 153 | s_row_of_bank <= (others => 0); 154 | 155 | -- Reset burst queue. 156 | for i in 0 to C_BURST_QUEUE_LEN-1 loop 157 | v_burst_queue(i).cmd := NONE; 158 | v_burst_queue(i).bank := 0; 159 | v_burst_queue(i).row := 0; 160 | v_burst_queue(i).col := 0; 161 | end loop; 162 | s_burst_idx <= 0; 163 | 164 | io_dq <= (others => 'Z'); 165 | elsif rising_edge(i_clk) then 166 | -- Decode command. 167 | v_cmd := i_cs_n & i_ras_n & i_cas_n & i_we_n; 168 | v_bank_no := to_integer(unsigned(i_ba(BANK_WIDTH-1 downto 0))); 169 | 170 | if v_cmd = C_CMD_MRS then 171 | -- Set the mode register (configure burst mode etc). 172 | s_mode_reg <= decode_mode_reg(unsigned(i_a)); 173 | elsif v_cmd = C_CMD_ACT then 174 | -- Activate the row of the given bank. 175 | s_row_of_bank(v_bank_no) <= to_integer(unsigned(i_a(ROW_WIDTH-1 downto 0))); 176 | elsif v_cmd = C_CMD_RD then 177 | -- Read burst from the given bank. 178 | v_row_no := s_row_of_bank(v_bank_no); 179 | v_col_no := decode_col(i_a); 180 | v_idx := (s_burst_idx + s_mode_reg.cas_latency) mod C_BURST_QUEUE_LEN; 181 | for i in 0 to s_mode_reg.burst_length-1 loop 182 | v_burst_queue(v_idx).cmd := RD; 183 | v_burst_queue(v_idx).bank := v_bank_no; 184 | v_burst_queue(v_idx).row := v_row_no; 185 | v_burst_queue(v_idx).col := v_col_no; 186 | v_col_no := v_col_no + 1; 187 | v_idx := (v_idx + 1) mod C_BURST_QUEUE_LEN; 188 | end loop; 189 | elsif v_cmd = C_CMD_WR then 190 | -- Write burst to the given bank. 191 | v_row_no := s_row_of_bank(v_bank_no); 192 | v_col_no := decode_col(i_a); 193 | v_idx := s_burst_idx; 194 | for i in 0 to s_mode_reg.burst_length-1 loop 195 | v_burst_queue(v_idx).cmd := WR; 196 | v_burst_queue(v_idx).bank := v_bank_no; 197 | v_burst_queue(v_idx).row := v_row_no; 198 | v_burst_queue(v_idx).col := v_col_no; 199 | v_col_no := v_col_no + 1; 200 | v_idx := (v_idx + 1) mod C_BURST_QUEUE_LEN; 201 | end loop; 202 | end if; 203 | 204 | -- Execute read/write commands from the burst queue. 205 | if v_burst_queue(s_burst_idx).cmd = RD then 206 | v_bank_no := v_burst_queue(s_burst_idx).bank; 207 | v_row_no := v_burst_queue(s_burst_idx).row; 208 | v_col_no := v_burst_queue(s_burst_idx).col; 209 | -- TODO(m): Honor i_dqm (output 'Z' if byte is inhibited by i_dqm). 210 | io_dq <= v_mem(v_bank_no)(v_row_no)(v_col_no); 211 | else 212 | if v_burst_queue(s_burst_idx).cmd = WR then 213 | v_bank_no := v_burst_queue(s_burst_idx).bank; 214 | v_row_no := v_burst_queue(s_burst_idx).row; 215 | v_col_no := v_burst_queue(s_burst_idx).col; 216 | 217 | -- Implement masked write (honor i_dqm). 218 | v_mem_data := v_mem(v_bank_no)(v_row_no)(v_col_no); 219 | v_mem_data := combine_with_mask(v_mem_data, io_dq, i_dqm); 220 | v_mem(v_bank_no)(v_row_no)(v_col_no) := v_mem_data; 221 | end if; 222 | io_dq <= (others => 'Z'); 223 | end if; 224 | 225 | -- Clear last currently executed burst item in the queue, and advance the queue position. 226 | -- Because it's a circular buffer, this has the effect of popping the front of the queue 227 | -- and pushing an empty item at the end of the queue. 228 | v_burst_queue(s_burst_idx).cmd := NONE; 229 | s_burst_idx <= (s_burst_idx + 1) mod C_BURST_QUEUE_LEN; 230 | end if; 231 | end process; 232 | end architecture behavioral; 233 | -------------------------------------------------------------------------------- /src/test/test-image-320x180-pal8.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrisc32/mc1/0ab36c80157da33d715ded4f562b379ff9cfe000/src/test/test-image-320x180-pal8.raw -------------------------------------------------------------------------------- /src/test/test-image-320x180-pal8.raw.pal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrisc32/mc1/0ab36c80157da33d715ded4f562b379ff9cfe000/src/test/test-image-320x180-pal8.raw.pal -------------------------------------------------------------------------------- /src/test/test-image-320x180-pal8.vcp: -------------------------------------------------------------------------------- 1 | ; -*- mode: vcpasm; tab-width: 4; indent-tabs-mode: nil; -*- 2 | ;----------------------------------------------------------------------------- 3 | ; This is a test program for video_tb. 4 | ;----------------------------------------------------------------------------- 5 | 6 | .include "mc1-defines.vcp" 7 | 8 | .set MODE_WIDTH, 320 9 | .set MODE_HEIGHT, 180 10 | 11 | ; Set the program start address 12 | .org 0x000004 13 | 14 | layer1_start: 15 | jmp main 16 | nop 17 | nop 18 | nop 19 | 20 | layer2_start: 21 | setpal 0, 1 22 | .word 0x00000000 23 | waity 32767 24 | nop 25 | 26 | main: 27 | ; Set the video mode 28 | setreg XINCR, 0x010000 * MODE_WIDTH / NATIVE_WIDTH 29 | setreg CMODE, CM_PAL8 30 | setreg RMODE, RM_DITHER_WHITE 31 | 32 | ; Set the palette 33 | jsr load_palette_a 34 | 35 | ; Activate video output starting at row 0. 36 | waity 0 37 | setreg HSTOP, NATIVE_WIDTH 38 | 39 | ; Generate video addresses for all rows. 40 | .set row, 0 41 | .set row_addr, image_data 42 | .set row_stride, MODE_WIDTH / 4 43 | .rept MODE_HEIGHT 44 | waity row 45 | setreg ADDR, row_addr 46 | .set row, row + (NATIVE_HEIGHT / MODE_HEIGHT) 47 | .set row_addr, row_addr + row_stride 48 | .endr 49 | 50 | ; End of program 51 | waity 32767 52 | 53 | load_palette_a: 54 | ; Load a palette with 256 colors. 55 | setpal 0, 256 56 | .incbin "test-image-320x180-pal8.raw.pal" 57 | rts 58 | 59 | image_data: 60 | .incbin "test-image-320x180-pal8.raw" 61 | 62 | -------------------------------------------------------------------------------- /src/test/test-image-640x360-pal8.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrisc32/mc1/0ab36c80157da33d715ded4f562b379ff9cfe000/src/test/test-image-640x360-pal8.raw -------------------------------------------------------------------------------- /src/test/test-image-640x360-pal8.raw.pal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrisc32/mc1/0ab36c80157da33d715ded4f562b379ff9cfe000/src/test/test-image-640x360-pal8.raw.pal -------------------------------------------------------------------------------- /src/test/test-image-640x360-pal8.vcp: -------------------------------------------------------------------------------- 1 | ; -*- mode: vcpasm; tab-width: 4; indent-tabs-mode: nil; -*- 2 | ;----------------------------------------------------------------------------- 3 | ; This is a test program for video_tb. 4 | ;----------------------------------------------------------------------------- 5 | 6 | .include "mc1-defines.vcp" 7 | 8 | .set MODE_WIDTH, 640 9 | .set MODE_HEIGHT, 360 10 | 11 | ; Set the program start address 12 | .org 0x000004 13 | 14 | layer1_start: 15 | jmp main 16 | nop 17 | nop 18 | nop 19 | 20 | layer2_start: 21 | setpal 0, 1 22 | .word 0x00000000 23 | waity 32767 24 | nop 25 | 26 | main: 27 | ; Set the video mode 28 | setreg XINCR, 0x010000 * MODE_WIDTH / NATIVE_WIDTH 29 | setreg CMODE, CM_PAL8 30 | setreg RMODE, RM_DITHER_WHITE 31 | 32 | ; Set the palette 33 | jsr load_palette_a 34 | 35 | ; Activate video output starting at row 0. 36 | waity 0 37 | setreg HSTOP, NATIVE_WIDTH 38 | 39 | ; Generate video addresses for all rows. 40 | .set row, 0 41 | .set row_addr, image_data 42 | .set row_stride, MODE_WIDTH / 4 43 | .rept MODE_HEIGHT 44 | waity row 45 | setreg ADDR, row_addr 46 | .set row, row + (NATIVE_HEIGHT / MODE_HEIGHT) 47 | .set row_addr, row_addr + row_stride 48 | .endr 49 | 50 | ; End of program 51 | waity 32767 52 | 53 | load_palette_a: 54 | ; Load a palette with 256 colors. 55 | setpal 0, 256 56 | .incbin "test-image-640x360-pal8.raw.pal" 57 | rts 58 | 59 | image_data: 60 | .incbin "test-image-640x360-pal8.raw" 61 | 62 | -------------------------------------------------------------------------------- /src/test/video_tb.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Copyright (c) 2019 Marcus Geelnard 3 | -- 4 | -- This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | -- authors be held liable for any damages arising from the use of this software. 6 | -- 7 | -- Permission is granted to anyone to use this software for any purpose, including commercial 8 | -- applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | -- 10 | -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote 11 | -- the original software. If you use this software in a product, an acknowledgment in the 12 | -- product documentation would be appreciated but is not required. 13 | -- 14 | -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 15 | -- being the original software. 16 | -- 17 | -- 3. This notice may not be removed or altered from any source distribution. 18 | ---------------------------------------------------------------------------------------------------- 19 | 20 | library vunit_lib; 21 | context vunit_lib.vunit_context; 22 | library ieee; 23 | use ieee.std_logic_1164.all; 24 | use ieee.numeric_std.all; 25 | library std; 26 | use std.textio.all; 27 | use work.vid_types.all; 28 | 29 | entity video_tb is 30 | generic (runner_cfg : string); 31 | end entity; 32 | 33 | architecture tb of video_tb is 34 | constant C_ADR_BITS : positive := 16; 35 | constant C_VRAM_WORDS : positive := 2**C_ADR_BITS; 36 | 37 | -- (640 + hblank) x (480 + vblank) = 420000 cycles 38 | -- (800 + hblank) x (600 + vblank) = 663168 cycles 39 | -- (1280 + hblank) x (720 + vblank) = 1237500 cycles 40 | -- (1920 + hblank) x (1080 + vblank) = 2475000 cycles 41 | constant C_TEST_CYCLES : integer := 2475000; 42 | 43 | -- 25.175 MHz -> 19.8609732 ns 44 | -- 40.000 MHz -> 12.5 ns 45 | -- 74.375 MHz -> 6.72268908 ns 46 | -- 148.500 MHz -> 3.36700337 ns 47 | constant C_CLK_HALF_PERIOD : time := 3.36700337 ns; 48 | 49 | signal s_write_clk : std_logic; 50 | signal s_write_cyc : std_logic; 51 | signal s_write_stb : std_logic; 52 | signal s_write_adr : std_logic_vector(C_ADR_BITS-1 downto 0); 53 | signal s_write_dat : std_logic_vector(31 downto 0); 54 | signal s_write_we : std_logic; 55 | 56 | signal s_rst : std_logic; 57 | signal s_clk : std_logic; 58 | signal s_read_adr : std_logic_vector(C_ADR_BITS-1 downto 0); 59 | signal s_read_dat : std_logic_vector(31 downto 0); 60 | signal s_r : std_logic_vector(3 downto 0); 61 | signal s_g : std_logic_vector(3 downto 0); 62 | signal s_b : std_logic_vector(3 downto 0); 63 | signal s_hsync : std_logic; 64 | signal s_vsync : std_logic; 65 | begin 66 | video_0: entity work.video 67 | generic map( 68 | COLOR_BITS_R => s_r'length, 69 | COLOR_BITS_G => s_g'length, 70 | COLOR_BITS_B => s_b'length, 71 | ADR_BITS => s_read_adr'length, 72 | NUM_LAYERS => 2, 73 | VIDEO_CONFIG => C_1920_1080 74 | ) 75 | port map( 76 | i_rst => s_rst, 77 | i_clk => s_clk, 78 | o_read_adr => s_read_adr, 79 | i_read_dat => s_read_dat, 80 | o_r => s_r, 81 | o_g => s_g, 82 | o_b => s_b, 83 | o_hsync => s_hsync, 84 | o_vsync => s_vsync 85 | ); 86 | 87 | vram_1: entity work.vram 88 | generic map ( 89 | ADR_BITS => s_read_adr'length 90 | ) 91 | port map ( 92 | i_rst => '0', 93 | 94 | -- CPU interface. 95 | i_wb_clk => s_write_clk, 96 | i_wb_cyc => s_write_cyc, 97 | i_wb_stb => s_write_stb, 98 | i_wb_adr => s_write_adr, 99 | i_wb_dat => s_write_dat, 100 | i_wb_we => s_write_we, 101 | i_wb_sel => "1111", 102 | 103 | -- Video interface. 104 | i_read_clk => s_clk, 105 | i_read_adr => s_read_adr, 106 | o_read_dat => s_read_dat 107 | ); 108 | 109 | main : process 110 | -- File I/O. 111 | type T_CHAR_FILE is file of character; 112 | file f_char_file : T_CHAR_FILE; 113 | 114 | -- Helper function for reading one word from a binary file. 115 | function read_word(file f : T_CHAR_FILE) return std_logic_vector is 116 | variable v_char : character; 117 | variable v_byte : std_logic_vector(7 downto 0); 118 | variable v_word : std_logic_vector(31 downto 0); 119 | begin 120 | for i in 0 to 3 loop 121 | read(f, v_char); 122 | v_byte := std_logic_vector(to_unsigned(character'pos(v_char), 8)); 123 | v_word(((i+1)*8)-1 downto i*8) := v_byte; 124 | end loop; 125 | return v_word; 126 | end function; 127 | 128 | -- Helper function for writing one word to a binary file. 129 | procedure write_word(file f : T_CHAR_FILE; word : std_logic_vector(31 downto 0)) is 130 | variable v_char : character; 131 | variable v_byte : std_logic_vector(7 downto 0); 132 | begin 133 | for i in 0 to 3 loop 134 | v_byte := word(((i+1)*8)-1 downto i*8); 135 | v_char := character'val(to_integer(unsigned(v_byte))); 136 | write(f, v_char); 137 | end loop; 138 | end procedure; 139 | 140 | -- Memory. 141 | variable v_mem_idx : integer; 142 | variable v_read_dat : std_logic_vector(31 downto 0); 143 | 144 | variable v_rgb_word : std_logic_vector(31 downto 0); 145 | begin 146 | test_runner_setup(runner, runner_cfg); 147 | 148 | -- Continue running even if we have failures (for easier debugging). 149 | set_stop_level(failure); 150 | 151 | -- Reset write signals. 152 | s_write_clk <= '0'; 153 | s_write_cyc <= '0'; 154 | s_write_stb <= '0'; 155 | s_write_we <= '0'; 156 | s_write_adr <= (others => '0'); 157 | s_write_dat <= (others => '0'); 158 | wait for 1 ps; 159 | 160 | -- Load data into VRAM. 161 | file_open(f_char_file, "vunit_out/video_tb_ram.bin"); 162 | v_mem_idx := 4; 163 | while not endfile(f_char_file) loop 164 | s_write_clk <= '1'; 165 | wait for 1 ps; 166 | 167 | -- Read one word from the data file and write it to VRAM. 168 | s_write_cyc <= '1'; 169 | s_write_stb <= '1'; 170 | s_write_we <= '1'; 171 | s_write_adr <= std_logic_vector(to_unsigned(v_mem_idx, C_ADR_BITS)); 172 | s_write_dat <= read_word(f_char_file); 173 | v_mem_idx := v_mem_idx + 1; 174 | 175 | -- Tick the write clock. 176 | s_write_clk <= '0'; 177 | wait for 1 ps; 178 | end loop; 179 | file_close(f_char_file); 180 | 181 | -- Finish the write cycle. 182 | s_write_cyc <= '1'; 183 | s_write_stb <= '0'; 184 | s_write_we <= '0'; 185 | s_write_clk <= '1'; 186 | wait for 1 ps; 187 | s_write_clk <= '0'; 188 | wait for 1 ps; 189 | s_write_cyc <= '1'; 190 | s_write_clk <= '1'; 191 | wait for 1 ps; 192 | s_write_clk <= '0'; 193 | wait for 1 ps; 194 | 195 | -- Reset the video logic. 196 | s_rst <= '1'; 197 | s_clk <= '0'; 198 | wait for C_CLK_HALF_PERIOD; 199 | s_clk <= '1'; 200 | wait for C_CLK_HALF_PERIOD; 201 | s_rst <= '0'; 202 | s_clk <= '0'; 203 | wait for C_CLK_HALF_PERIOD; 204 | 205 | -- Run a lot of cycles... 206 | file_open(f_char_file, "vunit_out/video_tb_output.data", WRITE_MODE); 207 | for i in 0 to C_TEST_CYCLES-1 loop 208 | -- Construct a word from the generated RGB output. 209 | -- We inject hsync and vsync into the color channels for visualization. 210 | v_rgb_word(31 downto 24) := 8x"ff"; 211 | v_rgb_word(23 downto 16) := s_b & s_b(3 downto 0); 212 | if s_vsync = '1' then 213 | v_rgb_word(15 downto 8) := 8x"ff"; 214 | else 215 | v_rgb_word(15 downto 8) := s_g & s_g(3 downto 0); 216 | end if; 217 | if s_hsync = '1' then 218 | v_rgb_word(7 downto 0) := 8x"ff"; 219 | else 220 | v_rgb_word(7 downto 0) := s_r & s_r(3 downto 0); 221 | end if; 222 | 223 | -- Write the word to the output file. 224 | write_word(f_char_file, v_rgb_word); 225 | 226 | -- Tick the clock. 227 | s_clk <= '1'; 228 | wait for C_CLK_HALF_PERIOD; 229 | s_clk <= '0'; 230 | wait for C_CLK_HALF_PERIOD; 231 | end loop; 232 | file_close(f_char_file); 233 | 234 | test_runner_cleanup(runner); 235 | end process; 236 | end architecture; 237 | --------------------------------------------------------------------------------