├── .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 | 
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 | 
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 | [](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 |
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 |
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 |
--------------------------------------------------------------------------------