Added DVI hstx examples, started work on mandelbrot
This commit is contained in:
parent
df6d93ac32
commit
9130db7d4f
@ -5,17 +5,22 @@ list(APPEND PICO_BOARD_HEADER_DIRS lib/pico-ice-sdk/include/boards)
|
|||||||
file(WRITE "${CMAKE_BINARY_DIR}/.gitignore" "*")
|
file(WRITE "${CMAKE_BINARY_DIR}/.gitignore" "*")
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
include(lib/pico-ice-sdk/cmake/preinit_pico_ice_sdk.cmake)
|
include(lib/pico-ice-sdk/cmake/preinit_pico_ice_sdk.cmake)
|
||||||
set(PICO_SDK_PATH ${CMAKE_SOURCE_DIR}/lib/pico-ice-sdk/lib/pico-sdk)
|
set(PICO_SDK_PATH ${CMAKE_SOURCE_DIR}/lib/pico-ice-sdk/lib/pico-sdk)
|
||||||
include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
|
include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
|
||||||
|
|
||||||
project(pico2-ice-projects C CXX ASM)
|
project(pico2-ice-projects C CXX ASM)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
|
|
||||||
pico_sdk_init()
|
pico_sdk_init()
|
||||||
|
|
||||||
|
include(cmake/FileEmbed.cmake)
|
||||||
|
FileEmbedSetup()
|
||||||
|
|
||||||
add_subdirectory(lib/pico-ice-sdk)
|
add_subdirectory(lib/pico-ice-sdk)
|
||||||
|
|
||||||
add_subdirectory(blinky)
|
add_subdirectory(blinky)
|
||||||
|
add_subdirectory(dvi-example)
|
||||||
|
add_subdirectory(dvi-dynamic)
|
||||||
|
88
cmake/FileEmbed.cmake
Normal file
88
cmake/FileEmbed.cmake
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
function(FileEmbedSetup)
|
||||||
|
|
||||||
|
if (NOT EXISTS ${CMAKE_BINARY_DIR}/file_embed)
|
||||||
|
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}file_embed)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (NOT EXISTS ${CMAKE_BINARY_DIR}/file_embed/file_embed_empty.c)
|
||||||
|
file(WRITE ${CMAKE_BINARY_DIR}/file_embed/file_embed_empty.c "")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_library(file_embed ${CMAKE_BINARY_DIR}/file_embed/file_embed_empty.c)
|
||||||
|
target_include_directories(file_embed PUBLIC ${CMAKE_BINARY_DIR}/file_embed)
|
||||||
|
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(FileEmbedAdd file)
|
||||||
|
FileEmbedGenerate(${file} var)
|
||||||
|
target_sources(file_embed PUBLIC ${var})
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${var}
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DRUN_FILE_EMBED_GENERATE=1
|
||||||
|
-DFILE_EMBED_GENERATE_PATH=${file}
|
||||||
|
-P ${CMAKE_SOURCE_DIR}/cmake/FileEmbed.cmake
|
||||||
|
MAIN_DEPENDENCY ${file}
|
||||||
|
)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(FileEmbedGenerate file generated_c)
|
||||||
|
|
||||||
|
get_filename_component(base_filename ${file} NAME)
|
||||||
|
set(output_filename "${base_filename}.c")
|
||||||
|
string(MAKE_C_IDENTIFIER ${base_filename} c_name)
|
||||||
|
file(READ ${file} content HEX)
|
||||||
|
message(${content})
|
||||||
|
|
||||||
|
# Separate into individual bytes.
|
||||||
|
string(REGEX MATCHALL "([A-Fa-f0-9][A-Fa-f0-9])" SEPARATED_HEX ${content})
|
||||||
|
|
||||||
|
set(output_c "")
|
||||||
|
|
||||||
|
set(counter 0)
|
||||||
|
foreach (hex IN LISTS SEPARATED_HEX)
|
||||||
|
string(APPEND output_c "0x${hex},")
|
||||||
|
MATH(EXPR counter "${counter}+1")
|
||||||
|
if (counter GREATER 16)
|
||||||
|
string(APPEND output_c "\n ")
|
||||||
|
set(counter 0)
|
||||||
|
endif ()
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
set(output_c "
|
||||||
|
#include \"${c_name}.h\"
|
||||||
|
uint8_t ${c_name}_data[] = {
|
||||||
|
${output_c}
|
||||||
|
}\;
|
||||||
|
unsigned ${c_name}_size = sizeof(${c_name}_data)\;
|
||||||
|
")
|
||||||
|
|
||||||
|
set(output_h "
|
||||||
|
#ifndef ${c_name}_H
|
||||||
|
#define ${c_name}_H
|
||||||
|
#include \"stdint.h\"
|
||||||
|
extern uint8_t ${c_name}_data[]\;
|
||||||
|
extern unsigned ${c_name}_size\;
|
||||||
|
#endif // ${c_name}_H
|
||||||
|
")
|
||||||
|
|
||||||
|
|
||||||
|
if (NOT EXISTS ${CMAKE_BINARY_DIR}/file_embed)
|
||||||
|
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}file_embed)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
file(WRITE ${CMAKE_BINARY_DIR}/file_embed/${c_name}.c
|
||||||
|
${output_c})
|
||||||
|
|
||||||
|
file(WRITE ${CMAKE_BINARY_DIR}/file_embed/${c_name}.h
|
||||||
|
${output_h})
|
||||||
|
|
||||||
|
set(${generated_c} ${CMAKE_BINARY_DIR}/file_embed/${c_name}.c PARENT_SCOPE)
|
||||||
|
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
if (RUN_FILE_EMBED_GENERATE)
|
||||||
|
FileEmbedGenerate(${FILE_EMBED_GENERATE_PATH} var)
|
||||||
|
endif ()
|
16
dvi-dynamic/CMakeLists.txt
Normal file
16
dvi-dynamic/CMakeLists.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13...3.27)
|
||||||
|
|
||||||
|
add_executable(dvi-dynamic
|
||||||
|
src/main.cpp
|
||||||
|
src/framebuffer.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
FileEmbedAdd(${CMAKE_CURRENT_SOURCE_DIR}/rtl_bin/mandelbrot.bin)
|
||||||
|
|
||||||
|
target_include_directories(dvi-dynamic PRIVATE include)
|
||||||
|
target_link_libraries(dvi-dynamic
|
||||||
|
pico_ice_sdk
|
||||||
|
hardware_dma
|
||||||
|
file_embed
|
||||||
|
)
|
||||||
|
pico_add_extra_outputs(dvi-dynamic)
|
12
dvi-dynamic/include/framebuffer.h
Normal file
12
dvi-dynamic/include/framebuffer.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#ifndef FRAMEBUFFER_H
|
||||||
|
#define FRAMEBUFFER_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
constexpr int FRAMEBUFFER_WIDTH = 640;
|
||||||
|
constexpr int FRAMEBUFFER_HEIGHT = 480;
|
||||||
|
constexpr int FRAMEBUFFER_SIZE = FRAMEBUFFER_WIDTH * FRAMEBUFFER_HEIGHT;
|
||||||
|
|
||||||
|
alignas(4) extern uint8_t FRAMEBUFFER[];
|
||||||
|
|
||||||
|
#endif//FRAMEBUFFER_H
|
17
dvi-dynamic/rtl/mandelbrot.rdf
Normal file
17
dvi-dynamic/rtl/mandelbrot.rdf
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<RadiantProject version="4.2" radiant="2024.2.0.3.0" title="mandelbrot" device="iCE40UP5K-SG48I" performance_grade="High-Performance_1.2V" family_int="ice40tp" device_int="itpa08" package_int="SG48" operation_int="IND" speed_int="6" default_implementation="impl_1">
|
||||||
|
<Options/>
|
||||||
|
<Implementation title="impl_1" dir="impl_1" description="impl_1" synthesis="synplify" default_strategy="Strategy1">
|
||||||
|
<Options VerilogStandard="System Verilog" top="top"/>
|
||||||
|
<Source name="source/impl_1/top.sv" type="Verilog" type_short="Verilog">
|
||||||
|
<Options VerilogStandard="System Verilog"/>
|
||||||
|
</Source>
|
||||||
|
<Source name="source/impl_1/impl_1.pdc" type="Physical Constraints File" type_short="PDC">
|
||||||
|
<Options/>
|
||||||
|
</Source>
|
||||||
|
<Source name="source/impl_1/impl_1.sdc" type="Pre-Synthesis Constraints File" type_short="SDC">
|
||||||
|
<Options/>
|
||||||
|
</Source>
|
||||||
|
</Implementation>
|
||||||
|
<Strategy name="Strategy1" file="mandelbrot1.sty"/>
|
||||||
|
</RadiantProject>
|
5
dvi-dynamic/rtl/source/impl_1/impl_1.pdc
Normal file
5
dvi-dynamic/rtl/source/impl_1/impl_1.pdc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
ldc_set_location -site 35 [get_ports {clk}]
|
||||||
|
|
||||||
|
ldc_set_location -site 39 [get_ports {led_g}]
|
||||||
|
ldc_set_location -site 40 [get_ports {led_b}]
|
||||||
|
ldc_set_location -site 41 [get_ports {led_r}]
|
1
dvi-dynamic/rtl/source/impl_1/impl_1.sdc
Normal file
1
dvi-dynamic/rtl/source/impl_1/impl_1.sdc
Normal file
@ -0,0 +1 @@
|
|||||||
|
create_clock -period 20.83333 [get_ports {clk}]
|
21
dvi-dynamic/rtl/source/impl_1/top.sv
Normal file
21
dvi-dynamic/rtl/source/impl_1/top.sv
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module top
|
||||||
|
(
|
||||||
|
input wire clk,
|
||||||
|
output wire led_r,
|
||||||
|
output wire led_g,
|
||||||
|
output wire led_b
|
||||||
|
);
|
||||||
|
|
||||||
|
localparam N = 22;
|
||||||
|
|
||||||
|
reg [N:0] counter;
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
counter <= counter + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
assign led_r = 1'b1;
|
||||||
|
assign led_g = counter[N];
|
||||||
|
assign led_b = 1'b1;
|
||||||
|
|
||||||
|
endmodule
|
1
dvi-dynamic/rtl_bin/.gitignore
vendored
Normal file
1
dvi-dynamic/rtl_bin/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.bin
|
3
dvi-dynamic/src/framebuffer.cpp
Normal file
3
dvi-dynamic/src/framebuffer.cpp
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#include "framebuffer.h"
|
||||||
|
|
||||||
|
alignas(4) uint8_t FRAMEBUFFER[FRAMEBUFFER_SIZE];
|
273
dvi-dynamic/src/main.cpp
Normal file
273
dvi-dynamic/src/main.cpp
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
// Copyright (c) 2024 Raspberry Pi (Trading) Ltd.
|
||||||
|
|
||||||
|
// Generate DVI output using the command expander and TMDS encoder in HSTX.
|
||||||
|
|
||||||
|
// This example requires an external digital video connector connected to
|
||||||
|
// GPIOs 12 through 19 (the HSTX-capable GPIOs) with appropriate
|
||||||
|
// current-limiting resistors, e.g. 270 ohms. The pinout used in this example
|
||||||
|
// matches the Pico DVI Sock board, which can be soldered onto a Pico 2:
|
||||||
|
// https://github.com/Wren6991/Pico-DVI-Sock
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include <hardware/dma.h>
|
||||||
|
#include <hardware/gpio.h>
|
||||||
|
#include <hardware/irq.h>
|
||||||
|
#include <hardware/structs/bus_ctrl.h>
|
||||||
|
#include <hardware/structs/hstx_ctrl.h>
|
||||||
|
#include <hardware/structs/hstx_fifo.h>
|
||||||
|
#include <ice_cram.h>
|
||||||
|
#include <ice_fpga.h>
|
||||||
|
#include <ice_led.h>
|
||||||
|
#include <pico/multicore.h>
|
||||||
|
|
||||||
|
#include "framebuffer.h"
|
||||||
|
|
||||||
|
#include "mandelbrot_bin.h"
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// DVI constants
|
||||||
|
|
||||||
|
#define TMDS_CTRL_00 0x354u
|
||||||
|
#define TMDS_CTRL_01 0x0abu
|
||||||
|
#define TMDS_CTRL_10 0x154u
|
||||||
|
#define TMDS_CTRL_11 0x2abu
|
||||||
|
|
||||||
|
#define SYNC_V0_H0 (TMDS_CTRL_00 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
|
||||||
|
#define SYNC_V0_H1 (TMDS_CTRL_01 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
|
||||||
|
#define SYNC_V1_H0 (TMDS_CTRL_10 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
|
||||||
|
#define SYNC_V1_H1 (TMDS_CTRL_11 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
|
||||||
|
|
||||||
|
#define MODE_H_SYNC_POLARITY 0
|
||||||
|
#define MODE_H_FRONT_PORCH 16
|
||||||
|
#define MODE_H_SYNC_WIDTH 96
|
||||||
|
#define MODE_H_BACK_PORCH 48
|
||||||
|
#define MODE_H_ACTIVE_PIXELS 640
|
||||||
|
|
||||||
|
#define MODE_V_SYNC_POLARITY 0
|
||||||
|
#define MODE_V_FRONT_PORCH 10
|
||||||
|
#define MODE_V_SYNC_WIDTH 2
|
||||||
|
#define MODE_V_BACK_PORCH 33
|
||||||
|
#define MODE_V_ACTIVE_LINES 480
|
||||||
|
|
||||||
|
#define MODE_H_TOTAL_PIXELS ( \
|
||||||
|
MODE_H_FRONT_PORCH + MODE_H_SYNC_WIDTH + \
|
||||||
|
MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS \
|
||||||
|
)
|
||||||
|
#define MODE_V_TOTAL_LINES ( \
|
||||||
|
MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + \
|
||||||
|
MODE_V_BACK_PORCH + MODE_V_ACTIVE_LINES \
|
||||||
|
)
|
||||||
|
|
||||||
|
#define HSTX_CMD_RAW (0x0u << 12)
|
||||||
|
#define HSTX_CMD_RAW_REPEAT (0x1u << 12)
|
||||||
|
#define HSTX_CMD_TMDS (0x2u << 12)
|
||||||
|
#define HSTX_CMD_TMDS_REPEAT (0x3u << 12)
|
||||||
|
#define HSTX_CMD_NOP (0xfu << 12)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// HSTX command lists
|
||||||
|
|
||||||
|
// Lists are padded with NOPs to be >= HSTX FIFO size, to avoid DMA rapidly
|
||||||
|
// pingponging and tripping up the IRQs.
|
||||||
|
|
||||||
|
static uint32_t vblank_line_vsync_off[] = {
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
|
||||||
|
SYNC_V1_H1,
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
|
||||||
|
SYNC_V1_H0,
|
||||||
|
HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS),
|
||||||
|
SYNC_V1_H1,
|
||||||
|
HSTX_CMD_NOP
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint32_t vblank_line_vsync_on[] = {
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
|
||||||
|
SYNC_V0_H1,
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
|
||||||
|
SYNC_V0_H0,
|
||||||
|
HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS),
|
||||||
|
SYNC_V0_H1,
|
||||||
|
HSTX_CMD_NOP
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint32_t vactive_line[] = {
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
|
||||||
|
SYNC_V1_H1,
|
||||||
|
HSTX_CMD_NOP,
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
|
||||||
|
SYNC_V1_H0,
|
||||||
|
HSTX_CMD_NOP,
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_BACK_PORCH,
|
||||||
|
SYNC_V1_H1,
|
||||||
|
HSTX_CMD_TMDS | MODE_H_ACTIVE_PIXELS
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// DMA logic
|
||||||
|
|
||||||
|
#define DMACH_PING 0
|
||||||
|
#define DMACH_PONG 1
|
||||||
|
|
||||||
|
// First we ping. Then we pong. Then... we ping again.
|
||||||
|
static bool dma_pong = false;
|
||||||
|
|
||||||
|
// A ping and a pong are cued up initially, so the first time we enter this
|
||||||
|
// handler it is to cue up the second ping after the first ping has completed.
|
||||||
|
// This is the third scanline overall (-> =2 because zero-based).
|
||||||
|
static uint v_scanline = 2;
|
||||||
|
|
||||||
|
// During the vertical active period, we take two IRQs per scanline: one to
|
||||||
|
// post the command list, and another to post the pixels.
|
||||||
|
static bool vactive_cmdlist_posted = false;
|
||||||
|
|
||||||
|
void __scratch_x("") dma_irq_handler() {
|
||||||
|
// dma_pong indicates the channel that just finished, which is the one
|
||||||
|
// we're about to reload.
|
||||||
|
uint ch_num = dma_pong ? DMACH_PONG : DMACH_PING;
|
||||||
|
dma_channel_hw_t *ch = &dma_hw->ch[ch_num];
|
||||||
|
dma_hw->intr = 1u << ch_num;
|
||||||
|
dma_pong = !dma_pong;
|
||||||
|
|
||||||
|
if (v_scanline >= MODE_V_FRONT_PORCH && v_scanline < (MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH)) {
|
||||||
|
ch->read_addr = (uintptr_t)vblank_line_vsync_on;
|
||||||
|
ch->transfer_count = count_of(vblank_line_vsync_on);
|
||||||
|
} else if (v_scanline < MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + MODE_V_BACK_PORCH) {
|
||||||
|
ch->read_addr = (uintptr_t)vblank_line_vsync_off;
|
||||||
|
ch->transfer_count = count_of(vblank_line_vsync_off);
|
||||||
|
} else if (!vactive_cmdlist_posted) {
|
||||||
|
ch->read_addr = (uintptr_t)vactive_line;
|
||||||
|
ch->transfer_count = count_of(vactive_line);
|
||||||
|
vactive_cmdlist_posted = true;
|
||||||
|
} else {
|
||||||
|
ch->read_addr = (uintptr_t)&FRAMEBUFFER[(v_scanline - (MODE_V_TOTAL_LINES - MODE_V_ACTIVE_LINES)) * MODE_H_ACTIVE_PIXELS];
|
||||||
|
ch->transfer_count = MODE_H_ACTIVE_PIXELS / sizeof(uint32_t);
|
||||||
|
vactive_cmdlist_posted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vactive_cmdlist_posted) {
|
||||||
|
v_scanline = (v_scanline + 1) % MODE_V_TOTAL_LINES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Main program
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
ice_led_init();
|
||||||
|
|
||||||
|
ice_fpga_init(FPGA_DATA, 48);
|
||||||
|
|
||||||
|
ice_cram_open(FPGA_DATA);
|
||||||
|
ice_cram_write(mandelbrot_bin_data, mandelbrot_bin_size);
|
||||||
|
ice_cram_close();
|
||||||
|
|
||||||
|
for (int i = 0; i < FRAMEBUFFER_SIZE; i++)
|
||||||
|
{
|
||||||
|
FRAMEBUFFER[i] = 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure HSTX's TMDS encoder for RGB332
|
||||||
|
hstx_ctrl_hw->expand_tmds =
|
||||||
|
2 << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB |
|
||||||
|
0 << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB |
|
||||||
|
2 << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB |
|
||||||
|
29 << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB |
|
||||||
|
1 << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB |
|
||||||
|
26 << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB;
|
||||||
|
|
||||||
|
// Pixels (TMDS) come in 4 8-bit chunks. Control symbols (RAW) are an
|
||||||
|
// entire 32-bit word.
|
||||||
|
hstx_ctrl_hw->expand_shift =
|
||||||
|
4 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB |
|
||||||
|
8 << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB |
|
||||||
|
1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB |
|
||||||
|
0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB;
|
||||||
|
|
||||||
|
// Serial output config: clock period of 5 cycles, pop from command
|
||||||
|
// expander every 5 cycles, shift the output shiftreg by 2 every cycle.
|
||||||
|
hstx_ctrl_hw->csr = 0;
|
||||||
|
hstx_ctrl_hw->csr =
|
||||||
|
HSTX_CTRL_CSR_EXPAND_EN_BITS |
|
||||||
|
5u << HSTX_CTRL_CSR_CLKDIV_LSB |
|
||||||
|
5u << HSTX_CTRL_CSR_N_SHIFTS_LSB |
|
||||||
|
2u << HSTX_CTRL_CSR_SHIFT_LSB |
|
||||||
|
HSTX_CTRL_CSR_EN_BITS;
|
||||||
|
|
||||||
|
// Note we are leaving the HSTX clock at the SDK default of 125 MHz; since
|
||||||
|
// we shift out two bits per HSTX clock cycle, this gives us an output of
|
||||||
|
// 250 Mbps, which is very close to the bit clock for 480p 60Hz (252 MHz).
|
||||||
|
// If we want the exact rate then we'll have to reconfigure PLLs.
|
||||||
|
|
||||||
|
// HSTX outputs 0 through 7 appear on GPIO 12 through 19.
|
||||||
|
// Pinout on Pico DVI sock:
|
||||||
|
//
|
||||||
|
// GP12 D0- GP13 D0+
|
||||||
|
// GP14 D1- GP15 D1+
|
||||||
|
// GP16 CK- GP17 CK+
|
||||||
|
// GP18 D2- GP19 D2+
|
||||||
|
|
||||||
|
// Assign clock pair to two neighbouring pins:
|
||||||
|
hstx_ctrl_hw->bit[5] = HSTX_CTRL_BIT0_CLK_BITS;
|
||||||
|
hstx_ctrl_hw->bit[4] = HSTX_CTRL_BIT0_CLK_BITS | HSTX_CTRL_BIT0_INV_BITS;
|
||||||
|
for (uint lane = 0; lane < 3; ++lane) {
|
||||||
|
// For each TMDS lane, assign it to the correct GPIO pair based on the
|
||||||
|
// desired pinout:
|
||||||
|
static const int lane_to_output_bit[3] = {0, 2, 6};
|
||||||
|
int bit = lane_to_output_bit[lane];
|
||||||
|
// Output even bits during first half of each HSTX cycle, and odd bits
|
||||||
|
// during second half. The shifter advances by two bits each cycle.
|
||||||
|
uint32_t lane_data_sel_bits =
|
||||||
|
(lane * 10 ) << HSTX_CTRL_BIT0_SEL_P_LSB |
|
||||||
|
(lane * 10 + 1) << HSTX_CTRL_BIT0_SEL_N_LSB;
|
||||||
|
// The two halves of each pair get identical data, but one pin is inverted.
|
||||||
|
hstx_ctrl_hw->bit[bit + 1] = lane_data_sel_bits;
|
||||||
|
hstx_ctrl_hw->bit[bit ] = lane_data_sel_bits | HSTX_CTRL_BIT0_INV_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 12; i <= 19; ++i) {
|
||||||
|
gpio_set_function(i, GPIO_FUNC_HSTX); // HSTX
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both channels are set up identically, to transfer a whole scanline and
|
||||||
|
// then chain to the opposite channel. Each time a channel finishes, we
|
||||||
|
// reconfigure the one that just finished, meanwhile the opposite channel
|
||||||
|
// is already making progress.
|
||||||
|
dma_channel_config c;
|
||||||
|
c = dma_channel_get_default_config(DMACH_PING);
|
||||||
|
channel_config_set_chain_to(&c, DMACH_PONG);
|
||||||
|
channel_config_set_dreq(&c, DREQ_HSTX);
|
||||||
|
dma_channel_configure(
|
||||||
|
DMACH_PING,
|
||||||
|
&c,
|
||||||
|
&hstx_fifo_hw->fifo,
|
||||||
|
vblank_line_vsync_off,
|
||||||
|
count_of(vblank_line_vsync_off),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
c = dma_channel_get_default_config(DMACH_PONG);
|
||||||
|
channel_config_set_chain_to(&c, DMACH_PING);
|
||||||
|
channel_config_set_dreq(&c, DREQ_HSTX);
|
||||||
|
dma_channel_configure(
|
||||||
|
DMACH_PONG,
|
||||||
|
&c,
|
||||||
|
&hstx_fifo_hw->fifo,
|
||||||
|
vblank_line_vsync_off,
|
||||||
|
count_of(vblank_line_vsync_off),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
dma_hw->ints0 = (1u << DMACH_PING) | (1u << DMACH_PONG);
|
||||||
|
dma_hw->inte0 = (1u << DMACH_PING) | (1u << DMACH_PONG);
|
||||||
|
irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler);
|
||||||
|
irq_set_enabled(DMA_IRQ_0, true);
|
||||||
|
|
||||||
|
bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS;
|
||||||
|
|
||||||
|
dma_channel_start(DMACH_PING);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
__wfi();
|
||||||
|
}
|
||||||
|
}
|
12
dvi-example/CMakeLists.txt
Normal file
12
dvi-example/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13...3.27)
|
||||||
|
|
||||||
|
add_executable(dvi-example
|
||||||
|
src/dvi_out_hstx_encoder.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(dvi-example PRIVATE src)
|
||||||
|
target_link_libraries(dvi-example
|
||||||
|
pico_ice_sdk
|
||||||
|
hardware_dma
|
||||||
|
)
|
||||||
|
pico_add_extra_outputs(dvi-example)
|
264
dvi-example/src/dvi_out_hstx_encoder.c
Normal file
264
dvi-example/src/dvi_out_hstx_encoder.c
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
// Copyright (c) 2024 Raspberry Pi (Trading) Ltd.
|
||||||
|
|
||||||
|
// Generate DVI output using the command expander and TMDS encoder in HSTX.
|
||||||
|
|
||||||
|
// This example requires an external digital video connector connected to
|
||||||
|
// GPIOs 12 through 19 (the HSTX-capable GPIOs) with appropriate
|
||||||
|
// current-limiting resistors, e.g. 270 ohms. The pinout used in this example
|
||||||
|
// matches the Pico DVI Sock board, which can be soldered onto a Pico 2:
|
||||||
|
// https://github.com/Wren6991/Pico-DVI-Sock
|
||||||
|
|
||||||
|
#include "hardware/dma.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/irq.h"
|
||||||
|
#include "hardware/structs/bus_ctrl.h"
|
||||||
|
#include "hardware/structs/hstx_ctrl.h"
|
||||||
|
#include "hardware/structs/hstx_fifo.h"
|
||||||
|
#include "hardware/structs/sio.h"
|
||||||
|
#include "pico/multicore.h"
|
||||||
|
#include "pico/sem.h"
|
||||||
|
|
||||||
|
#include "mountains_640x480_rgb332.h"
|
||||||
|
#define framebuf mountains_640x480
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// DVI constants
|
||||||
|
|
||||||
|
#define TMDS_CTRL_00 0x354u
|
||||||
|
#define TMDS_CTRL_01 0x0abu
|
||||||
|
#define TMDS_CTRL_10 0x154u
|
||||||
|
#define TMDS_CTRL_11 0x2abu
|
||||||
|
|
||||||
|
#define SYNC_V0_H0 (TMDS_CTRL_00 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
|
||||||
|
#define SYNC_V0_H1 (TMDS_CTRL_01 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
|
||||||
|
#define SYNC_V1_H0 (TMDS_CTRL_10 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
|
||||||
|
#define SYNC_V1_H1 (TMDS_CTRL_11 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
|
||||||
|
|
||||||
|
#define MODE_H_SYNC_POLARITY 0
|
||||||
|
#define MODE_H_FRONT_PORCH 16
|
||||||
|
#define MODE_H_SYNC_WIDTH 96
|
||||||
|
#define MODE_H_BACK_PORCH 48
|
||||||
|
#define MODE_H_ACTIVE_PIXELS 640
|
||||||
|
|
||||||
|
#define MODE_V_SYNC_POLARITY 0
|
||||||
|
#define MODE_V_FRONT_PORCH 10
|
||||||
|
#define MODE_V_SYNC_WIDTH 2
|
||||||
|
#define MODE_V_BACK_PORCH 33
|
||||||
|
#define MODE_V_ACTIVE_LINES 480
|
||||||
|
|
||||||
|
#define MODE_H_TOTAL_PIXELS ( \
|
||||||
|
MODE_H_FRONT_PORCH + MODE_H_SYNC_WIDTH + \
|
||||||
|
MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS \
|
||||||
|
)
|
||||||
|
#define MODE_V_TOTAL_LINES ( \
|
||||||
|
MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + \
|
||||||
|
MODE_V_BACK_PORCH + MODE_V_ACTIVE_LINES \
|
||||||
|
)
|
||||||
|
|
||||||
|
#define HSTX_CMD_RAW (0x0u << 12)
|
||||||
|
#define HSTX_CMD_RAW_REPEAT (0x1u << 12)
|
||||||
|
#define HSTX_CMD_TMDS (0x2u << 12)
|
||||||
|
#define HSTX_CMD_TMDS_REPEAT (0x3u << 12)
|
||||||
|
#define HSTX_CMD_NOP (0xfu << 12)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// HSTX command lists
|
||||||
|
|
||||||
|
// Lists are padded with NOPs to be >= HSTX FIFO size, to avoid DMA rapidly
|
||||||
|
// pingponging and tripping up the IRQs.
|
||||||
|
|
||||||
|
static uint32_t vblank_line_vsync_off[] = {
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
|
||||||
|
SYNC_V1_H1,
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
|
||||||
|
SYNC_V1_H0,
|
||||||
|
HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS),
|
||||||
|
SYNC_V1_H1,
|
||||||
|
HSTX_CMD_NOP
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint32_t vblank_line_vsync_on[] = {
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
|
||||||
|
SYNC_V0_H1,
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
|
||||||
|
SYNC_V0_H0,
|
||||||
|
HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS),
|
||||||
|
SYNC_V0_H1,
|
||||||
|
HSTX_CMD_NOP
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint32_t vactive_line[] = {
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
|
||||||
|
SYNC_V1_H1,
|
||||||
|
HSTX_CMD_NOP,
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
|
||||||
|
SYNC_V1_H0,
|
||||||
|
HSTX_CMD_NOP,
|
||||||
|
HSTX_CMD_RAW_REPEAT | MODE_H_BACK_PORCH,
|
||||||
|
SYNC_V1_H1,
|
||||||
|
HSTX_CMD_TMDS | MODE_H_ACTIVE_PIXELS
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// DMA logic
|
||||||
|
|
||||||
|
#define DMACH_PING 0
|
||||||
|
#define DMACH_PONG 1
|
||||||
|
|
||||||
|
// First we ping. Then we pong. Then... we ping again.
|
||||||
|
static bool dma_pong = false;
|
||||||
|
|
||||||
|
// A ping and a pong are cued up initially, so the first time we enter this
|
||||||
|
// handler it is to cue up the second ping after the first ping has completed.
|
||||||
|
// This is the third scanline overall (-> =2 because zero-based).
|
||||||
|
static uint v_scanline = 2;
|
||||||
|
|
||||||
|
// During the vertical active period, we take two IRQs per scanline: one to
|
||||||
|
// post the command list, and another to post the pixels.
|
||||||
|
static bool vactive_cmdlist_posted = false;
|
||||||
|
|
||||||
|
void __scratch_x("") dma_irq_handler() {
|
||||||
|
// dma_pong indicates the channel that just finished, which is the one
|
||||||
|
// we're about to reload.
|
||||||
|
uint ch_num = dma_pong ? DMACH_PONG : DMACH_PING;
|
||||||
|
dma_channel_hw_t *ch = &dma_hw->ch[ch_num];
|
||||||
|
dma_hw->intr = 1u << ch_num;
|
||||||
|
dma_pong = !dma_pong;
|
||||||
|
|
||||||
|
if (v_scanline >= MODE_V_FRONT_PORCH && v_scanline < (MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH)) {
|
||||||
|
ch->read_addr = (uintptr_t)vblank_line_vsync_on;
|
||||||
|
ch->transfer_count = count_of(vblank_line_vsync_on);
|
||||||
|
} else if (v_scanline < MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + MODE_V_BACK_PORCH) {
|
||||||
|
ch->read_addr = (uintptr_t)vblank_line_vsync_off;
|
||||||
|
ch->transfer_count = count_of(vblank_line_vsync_off);
|
||||||
|
} else if (!vactive_cmdlist_posted) {
|
||||||
|
ch->read_addr = (uintptr_t)vactive_line;
|
||||||
|
ch->transfer_count = count_of(vactive_line);
|
||||||
|
vactive_cmdlist_posted = true;
|
||||||
|
} else {
|
||||||
|
ch->read_addr = (uintptr_t)&framebuf[(v_scanline - (MODE_V_TOTAL_LINES - MODE_V_ACTIVE_LINES)) * MODE_H_ACTIVE_PIXELS];
|
||||||
|
ch->transfer_count = MODE_H_ACTIVE_PIXELS / sizeof(uint32_t);
|
||||||
|
vactive_cmdlist_posted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vactive_cmdlist_posted) {
|
||||||
|
v_scanline = (v_scanline + 1) % MODE_V_TOTAL_LINES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Main program
|
||||||
|
|
||||||
|
static __force_inline uint16_t colour_rgb565(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
return ((uint16_t)r & 0xf8) >> 3 | ((uint16_t)g & 0xfc) << 3 | ((uint16_t)b & 0xf8) << 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __force_inline uint8_t colour_rgb332(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
return (r & 0xc0) >> 6 | (g & 0xe0) >> 3 | (b & 0xe0) >> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scroll_framebuffer(void);
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
// Configure HSTX's TMDS encoder for RGB332
|
||||||
|
hstx_ctrl_hw->expand_tmds =
|
||||||
|
2 << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB |
|
||||||
|
0 << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB |
|
||||||
|
2 << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB |
|
||||||
|
29 << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB |
|
||||||
|
1 << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB |
|
||||||
|
26 << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB;
|
||||||
|
|
||||||
|
// Pixels (TMDS) come in 4 8-bit chunks. Control symbols (RAW) are an
|
||||||
|
// entire 32-bit word.
|
||||||
|
hstx_ctrl_hw->expand_shift =
|
||||||
|
4 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB |
|
||||||
|
8 << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB |
|
||||||
|
1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB |
|
||||||
|
0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB;
|
||||||
|
|
||||||
|
// Serial output config: clock period of 5 cycles, pop from command
|
||||||
|
// expander every 5 cycles, shift the output shiftreg by 2 every cycle.
|
||||||
|
hstx_ctrl_hw->csr = 0;
|
||||||
|
hstx_ctrl_hw->csr =
|
||||||
|
HSTX_CTRL_CSR_EXPAND_EN_BITS |
|
||||||
|
5u << HSTX_CTRL_CSR_CLKDIV_LSB |
|
||||||
|
5u << HSTX_CTRL_CSR_N_SHIFTS_LSB |
|
||||||
|
2u << HSTX_CTRL_CSR_SHIFT_LSB |
|
||||||
|
HSTX_CTRL_CSR_EN_BITS;
|
||||||
|
|
||||||
|
// Note we are leaving the HSTX clock at the SDK default of 125 MHz; since
|
||||||
|
// we shift out two bits per HSTX clock cycle, this gives us an output of
|
||||||
|
// 250 Mbps, which is very close to the bit clock for 480p 60Hz (252 MHz).
|
||||||
|
// If we want the exact rate then we'll have to reconfigure PLLs.
|
||||||
|
|
||||||
|
// HSTX outputs 0 through 7 appear on GPIO 12 through 19.
|
||||||
|
// Pinout on Pico DVI sock:
|
||||||
|
//
|
||||||
|
// GP12 D0- GP13 D0+
|
||||||
|
// GP14 D1- GP15 D1+
|
||||||
|
// GP16 CK- GP17 CK+
|
||||||
|
// GP18 D2- GP19 D2+
|
||||||
|
|
||||||
|
// Assign clock pair to two neighbouring pins:
|
||||||
|
hstx_ctrl_hw->bit[5] = HSTX_CTRL_BIT0_CLK_BITS;
|
||||||
|
hstx_ctrl_hw->bit[4] = HSTX_CTRL_BIT0_CLK_BITS | HSTX_CTRL_BIT0_INV_BITS;
|
||||||
|
for (uint lane = 0; lane < 3; ++lane) {
|
||||||
|
// For each TMDS lane, assign it to the correct GPIO pair based on the
|
||||||
|
// desired pinout:
|
||||||
|
static const int lane_to_output_bit[3] = {0, 2, 6};
|
||||||
|
int bit = lane_to_output_bit[lane];
|
||||||
|
// Output even bits during first half of each HSTX cycle, and odd bits
|
||||||
|
// during second half. The shifter advances by two bits each cycle.
|
||||||
|
uint32_t lane_data_sel_bits =
|
||||||
|
(lane * 10 ) << HSTX_CTRL_BIT0_SEL_P_LSB |
|
||||||
|
(lane * 10 + 1) << HSTX_CTRL_BIT0_SEL_N_LSB;
|
||||||
|
// The two halves of each pair get identical data, but one pin is inverted.
|
||||||
|
hstx_ctrl_hw->bit[bit + 1] = lane_data_sel_bits;
|
||||||
|
hstx_ctrl_hw->bit[bit ] = lane_data_sel_bits | HSTX_CTRL_BIT0_INV_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 12; i <= 19; ++i) {
|
||||||
|
gpio_set_function(i, 0); // HSTX
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both channels are set up identically, to transfer a whole scanline and
|
||||||
|
// then chain to the opposite channel. Each time a channel finishes, we
|
||||||
|
// reconfigure the one that just finished, meanwhile the opposite channel
|
||||||
|
// is already making progress.
|
||||||
|
dma_channel_config c;
|
||||||
|
c = dma_channel_get_default_config(DMACH_PING);
|
||||||
|
channel_config_set_chain_to(&c, DMACH_PONG);
|
||||||
|
channel_config_set_dreq(&c, DREQ_HSTX);
|
||||||
|
dma_channel_configure(
|
||||||
|
DMACH_PING,
|
||||||
|
&c,
|
||||||
|
&hstx_fifo_hw->fifo,
|
||||||
|
vblank_line_vsync_off,
|
||||||
|
count_of(vblank_line_vsync_off),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
c = dma_channel_get_default_config(DMACH_PONG);
|
||||||
|
channel_config_set_chain_to(&c, DMACH_PING);
|
||||||
|
channel_config_set_dreq(&c, DREQ_HSTX);
|
||||||
|
dma_channel_configure(
|
||||||
|
DMACH_PONG,
|
||||||
|
&c,
|
||||||
|
&hstx_fifo_hw->fifo,
|
||||||
|
vblank_line_vsync_off,
|
||||||
|
count_of(vblank_line_vsync_off),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
dma_hw->ints0 = (1u << DMACH_PING) | (1u << DMACH_PONG);
|
||||||
|
dma_hw->inte0 = (1u << DMACH_PING) | (1u << DMACH_PONG);
|
||||||
|
irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler);
|
||||||
|
irq_set_enabled(DMA_IRQ_0, true);
|
||||||
|
|
||||||
|
bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS;
|
||||||
|
|
||||||
|
dma_channel_start(DMACH_PING);
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
__wfi();
|
||||||
|
}
|
19207
dvi-example/src/mountains_640x480_rgb332.h
Normal file
19207
dvi-example/src/mountains_640x480_rgb332.h
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user