From ff2dcc7d715186583b6c0cfd4bba88ff599e25f5 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Thu, 30 Nov 2023 10:02:22 +0800 Subject: [PATCH] draw 1 triangle --- addresses.lds | 26 ++++++ alt.lds | 12 +-- cache.c | 25 ------ cache.cpp | 25 ++++++ client.py | 6 ++ common.mk | 29 ++++++- holly.h | 3 +- holly/background.cpp | 49 +++++++++++ holly/background.h | 5 ++ holly/float_uint32.h | 10 +++ holly/isp_tsp.h | 113 +++++++++++++++++++++++++ holly/region_array.cpp | 61 ++++++++++++++ holly/region_array.h | 8 ++ holly/ta_parameter.cpp | 185 +++++++++++++++++++++++++++++++++++++++++ holly/ta_parameter.h | 15 ++++ load.c => load.cpp | 38 ++++----- main.c | 66 --------------- main.cpp | 96 +++++++++++++++++++++ main.lds | 8 +- memorymap.h | 13 +++ notes.txt | 176 +++++++++++++++++++++++++++++++++++++++ regs/gen/generate.py | 9 +- regs/gen/holly.py | 2 +- regs/gen/memorymap.py | 42 ++++++++++ regs/gen/sh7091.py | 2 +- regs/memorymap.csv | 43 ++++++++++ regs/memorymap.ods | Bin 16934 -> 17212 bytes rgb.c => rgb.cpp | 0 scene.cpp | 153 ++++++++++++++++++++++++++++++++++ scene.h | 8 ++ sh7091.h | 3 +- sh7091_bits.h | 15 ++++ storequeue.cpp | 34 ++++++++ storequeue.h | 4 + systembus.h | 11 ++- ta.h | 88 ++++++++++++++++++++ test.c | 59 +++++++------ vga.c => vga.cpp | 81 +++++++++--------- 38 files changed, 1312 insertions(+), 211 deletions(-) create mode 100644 addresses.lds delete mode 100644 cache.c create mode 100644 cache.cpp create mode 100644 holly/background.cpp create mode 100644 holly/background.h create mode 100644 holly/float_uint32.h create mode 100644 holly/isp_tsp.h create mode 100644 holly/region_array.cpp create mode 100644 holly/region_array.h create mode 100644 holly/ta_parameter.cpp create mode 100644 holly/ta_parameter.h rename load.c => load.cpp (72%) delete mode 100644 main.c create mode 100644 main.cpp create mode 100644 memorymap.h create mode 100644 notes.txt create mode 100644 regs/gen/memorymap.py create mode 100644 regs/memorymap.csv rename rgb.c => rgb.cpp (100%) create mode 100644 scene.cpp create mode 100644 scene.h create mode 100644 storequeue.cpp create mode 100644 storequeue.h create mode 100644 ta.h rename vga.c => vga.cpp (63%) diff --git a/addresses.lds b/addresses.lds new file mode 100644 index 0000000..e4e20d5 --- /dev/null +++ b/addresses.lds @@ -0,0 +1,26 @@ +sh7091_ic_a = 0xf0000000; +sh7091_ic_d = 0xf1000000; +sh7091_oc_a = 0xf4000000; +sh7091_oc_d = 0xf5000000; +sh7091 = 0xff000000; + +system = 0xa05f6800; +maple_if = 0xa05f6c00; +gdrom_if = 0xa05f7000; +g1_if = 0xa05f7400; +g2_if = 0xa05f7800; +pvr_if = 0xa05f7c00; +holly = 0xa05f8000; +modem = 0xa0600000; + +system_boot_rom = 0xa0000000; +aica_wave_memory = 0xa0800000; +texture_memory = 0xa5000000; +system_memory = 0xac000000; +ta_fifo_polygon_converter = 0x10000000; +ta_fifo_yuv_converter = 0x10800000; +ta_fifo_texture_memory = 0x11000000; +ta_fifo_polygon_converter_mirror = 0x12000000; +ta_fifo_yuv_converter_mirror = 0x12800000; +ta_fifo_texture_memory_mirror = 0x13000000; +store_queue = 0xe0000000; diff --git a/alt.lds b/alt.lds index db089da..71bdccc 100644 --- a/alt.lds +++ b/alt.lds @@ -58,14 +58,4 @@ SECTIONS __p1ram_start = ORIGIN(p1ram); __p1ram_end = ORIGIN(p1ram) + LENGTH(p1ram); -SH7091_IC_A = 0xf0000000; -SH7091_IC_D = 0xf1000000; -SH7091_OC_A = 0xf4000000; -SH7091_OC_D = 0xf5000000; -SH7091 = 0xff000000; -HOLLY = 0xa05f8000; -SYSTEM = 0xa05F6800; -MAPLE_IF = 0xa05F6C00; -G1_IF = 0xa05F7400; -G2_IF = 0xa05F7800; -PVR_IF = 0xa05F7C00; +INCLUDE "addresses.lds" diff --git a/cache.c b/cache.c deleted file mode 100644 index 5677742..0000000 --- a/cache.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "type.h" -#include "sh7091.h" -#include "sh7091_bits.h" - -#include "cache.h" - -extern volatile reg32 SH7091_IC_A[256][(1 << 5) / 4] __asm("SH7091_IC_A"); -extern volatile reg32 SH7091_OC_A[512][(1 << 5) / 4] __asm("SH7091_OC_A"); - -void cache_init() -{ - for (int i = 0; i < 256; i++) { - SH7091_IC_A[i][0] = 0; - } - - for (int i = 0; i < 512; i++) { - SH7091_OC_A[i][0] = 0; - } - - SH7091.CCN.CCR = CCR__ICI | CCR__ICE | CCR__OCI | CCR__OCE; - - SH7091.CCN.MMUCR = 0; - - asm volatile ("nop;nop;nop;nop;nop;nop;nop;nop;"); -} diff --git a/cache.cpp b/cache.cpp new file mode 100644 index 0000000..a72b456 --- /dev/null +++ b/cache.cpp @@ -0,0 +1,25 @@ +#include "type.h" +#include "sh7091.h" +#include "sh7091_bits.h" + +#include "cache.h" + +extern volatile reg32 sh7091_ic_a[256][(1 << 5) / 4] __asm("sh7091_ic_a"); +extern volatile reg32 sh7091_oc_a[512][(1 << 5) / 4] __asm("sh7091_oc_a"); + +void cache_init() +{ + for (int i = 0; i < 256; i++) { + sh7091_ic_a[i][0] = 0; + } + + for (int i = 0; i < 512; i++) { + sh7091_oc_a[i][0] = 0; + } + + sh7091.CCN.CCR = CCR__ICI | CCR__ICE | CCR__OCI | CCR__OCE; + + sh7091.CCN.MMUCR = 0; + + asm volatile ("nop;nop;nop;nop;nop;nop;nop;nop;"); +} diff --git a/client.py b/client.py index c88a1aa..49baa91 100644 --- a/client.py +++ b/client.py @@ -32,6 +32,7 @@ def sync(ser, b): return bytes(l) def do(ser, b): + ser.read(ser.in_waiting) ser.flush() ser.flushInput() ser.flushOutput() @@ -55,14 +56,19 @@ def do(ser, b): args = struct.pack("/dev/null +%.data.o: %.data + $(BUILD_BINARY_O) + .SUFFIXES: .INTERMEDIATE: .SECONDARY: diff --git a/holly.h b/holly.h index e1f1b7c..7287787 100644 --- a/holly.h +++ b/holly.h @@ -164,5 +164,4 @@ static_assert((offsetof (struct holly_reg, FOG_TABLE)) == 0x200); static_assert((offsetof (struct holly_reg, TA_OL_POINTERS)) == 0x600); static_assert((offsetof (struct holly_reg, PALETTE_RAM)) == 0x1000); -extern struct holly_reg HOLLY __asm("HOLLY"); - +extern struct holly_reg holly __asm("holly"); diff --git a/holly/background.cpp b/holly/background.cpp new file mode 100644 index 0000000..68427b8 --- /dev/null +++ b/holly/background.cpp @@ -0,0 +1,49 @@ +#include + +#include "isp_tsp.h" + +struct vertex_parameter { + float x; + float y; + float z; + uint32_t base_color; +}; // ISP_BACKGND_T skip(1) + +struct isp_tsp_parameter { + uint32_t isp_tsp_instruction_word; + uint32_t tsp_instruction_word; + uint32_t texture_control_word; + vertex_parameter vertex[3]; +}; + +void background_parameter(volatile uint32_t * buf) +{ + volatile isp_tsp_parameter * parameter = reinterpret_cast(buf); + + parameter->isp_tsp_instruction_word + = isp_tsp_instruction_word::depth_compare_mode::always + | isp_tsp_instruction_word::culling_mode::no_culling; + + parameter->tsp_instruction_word + = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog; + + parameter->texture_control_word + = 0; + + parameter->vertex[0].x = 0.f; + parameter->vertex[0].y = 0.f; + parameter->vertex[0].z = 1.f/100000; + parameter->vertex[0].base_color = 0xff300000; + + parameter->vertex[1].x = 0.f; + parameter->vertex[1].y = 480.f; + parameter->vertex[1].z = 1.f/100000; + parameter->vertex[1].base_color = 0xff300000; + + parameter->vertex[2].x = 640.f; + parameter->vertex[2].y = 0.f; + parameter->vertex[2].z = 1.f/100000; + parameter->vertex[2].base_color = 0xff300000; +} diff --git a/holly/background.h b/holly/background.h new file mode 100644 index 0000000..266d44f --- /dev/null +++ b/holly/background.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void background_parameter(volatile uint32_t * buf); diff --git a/holly/float_uint32.h b/holly/float_uint32.h new file mode 100644 index 0000000..56c7bbf --- /dev/null +++ b/holly/float_uint32.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +constexpr uint32_t _i(float f) { + return *(reinterpret_cast(&f)); +} +#pragma GCC diagnostic pop diff --git a/holly/isp_tsp.h b/holly/isp_tsp.h new file mode 100644 index 0000000..fb8579a --- /dev/null +++ b/holly/isp_tsp.h @@ -0,0 +1,113 @@ +#include + +namespace isp_tsp_instruction_word { + namespace depth_compare_mode { + constexpr uint32_t never = 0 << 29; + constexpr uint32_t less = 1 << 29; + constexpr uint32_t equal = 2 << 29; + constexpr uint32_t less_or_equal = 3 << 29; + constexpr uint32_t greater = 4 << 29; + constexpr uint32_t not_equal = 5 << 29; + constexpr uint32_t greater_or_equal = 6 << 29; + constexpr uint32_t always = 7 << 29; + } + + namespace culling_mode { + constexpr uint32_t no_culling = 0 << 27; + constexpr uint32_t cull_if_small = 1 << 27; // compared to FPU_CULL_VAL + constexpr uint32_t cull_if_negative = 2 << 27; + constexpr uint32_t cull_if_positive = 3 << 27; + } + + constexpr uint32_t z_write_disable = 1 << 26; + constexpr uint32_t texture = 1 << 25; + constexpr uint32_t offset = 1 << 24; + constexpr uint32_t gouraud_shading = 1 << 23; + constexpr uint32_t _16bit_uv = 1 << 22; + constexpr uint32_t cache_bypass = 1 << 21; + constexpr uint32_t dcalc_ctrl = 1 << 20; +} + +namespace tsp_instruction_word { + namespace src_alpha_instr { + constexpr uint32_t zero = 0 << 29; + constexpr uint32_t one = 1 << 29; + constexpr uint32_t other_color = 2 << 29; + constexpr uint32_t inverse_other_color = 3 << 29; + constexpr uint32_t src_alpha = 4 << 29; + constexpr uint32_t inverse_src_alpha = 5 << 29; + constexpr uint32_t dst_alpha = 6 << 29; + constexpr uint32_t inverse_dst_alpha = 7 << 29; + } + + namespace dst_alpha_instr { + constexpr uint32_t zero = 0 << 26; + constexpr uint32_t one = 1 << 26; + constexpr uint32_t other_color = 2 << 26; + constexpr uint32_t inverse_other_color = 3 << 26; + constexpr uint32_t src_alpha = 4 << 26; + constexpr uint32_t inverse_src_alpha = 5 << 26; + constexpr uint32_t dst_alpha = 6 << 26; + constexpr uint32_t inverse_dst_alpha = 7 << 26; + } + + constexpr uint32_t src_select = 1 << 25; + constexpr uint32_t dst_select = 1 << 24; + + namespace fog_control { + constexpr uint32_t look_up_table = 0b00 << 22; + constexpr uint32_t per_vertex = 0b01 << 22; + constexpr uint32_t no_fog = 0b10 << 22; + constexpr uint32_t look_up_table_mode_2 = 0b11 << 22; + } + + constexpr uint32_t color_clamp = 1 << 21; + constexpr uint32_t use_alpha = 1 << 20; + constexpr uint32_t ignore_tex_alpha = 1 << 19; + + // flip_uv + // clamp_uv + + namespace filter_mode { + constexpr uint32_t point_sampled = 0b00 << 13; + constexpr uint32_t bilinear_filter = 0b01 << 13; + constexpr uint32_t trilinear_pass_a = 0b10 << 13; + constexpr uint32_t trilinear_pass_b = 0b11 << 13; + } + + constexpr uint32_t super_sample_texture = 1 << 12; + + constexpr uint32_t mip_map_d_adjust(uint32_t fp) + { + return (fp & 0b1111) << 8; + } + + namespace texture_shading_instruction { + constexpr uint32_t decal = 0 << 6; + constexpr uint32_t modulate = 1 << 6; + constexpr uint32_t decal_alpha = 2 << 6; + constexpr uint32_t modulate_alpha = 3 << 6; + } + + namespace texture_u_size { + constexpr uint32_t _8 = 0 << 3; + constexpr uint32_t _16 = 1 << 3; + constexpr uint32_t _32 = 2 << 3; + constexpr uint32_t _64 = 3 << 3; + constexpr uint32_t _128 = 4 << 3; + constexpr uint32_t _256 = 5 << 3; + constexpr uint32_t _512 = 6 << 3; + constexpr uint32_t _1024 = 7 << 3; + } + + namespace texture_v_size { + constexpr uint32_t _8 = 0 << 0; + constexpr uint32_t _16 = 1 << 0; + constexpr uint32_t _32 = 2 << 0; + constexpr uint32_t _64 = 3 << 0; + constexpr uint32_t _128 = 4 << 0; + constexpr uint32_t _256 = 5 << 0; + constexpr uint32_t _512 = 6 << 0; + constexpr uint32_t _1024 = 7 << 0; + } +} diff --git a/holly/region_array.cpp b/holly/region_array.cpp new file mode 100644 index 0000000..3052b79 --- /dev/null +++ b/holly/region_array.cpp @@ -0,0 +1,61 @@ +#include "region_array.h" + +#define REGION_ARRAY__LAST_REGION (1 << 31) +#define REGION_ARRAY__Z_CLEAR (1 << 30) +#define REGION_ARRAY__PRE_SORT (1 << 29) +#define REGION_ARRAY__FLUSH_ACCUMULATE (1 << 28) +#define REGION_ARRAY__TILE_Y_POSITION(n) (((n) & 0x3f) << 8) +#define REGION_ARRAY__TILE_X_POSITION(n) (((n) & 0x3f) << 2) + +#define REGION_ARRAY__LIST_POINTER__EMPTY (1 << 31) +#define REGION_ARRAY__LIST_POINTER(n) ((n) & 0xfffffc) + +// this is for a "type 2" region array. +// region header type is specified in FPU_PARAM_CFG +struct region_array_entry { + uint32_t tile; /* 3.7.7 page 216 */ + uint32_t opaque_list_pointer; + uint32_t opaque_modifier_volume_list_pointer; + uint32_t translucent_list_pointer; + uint32_t translucent_modifier_volume_list_pointer; + uint32_t punch_through_list_pointer; +}; + +// opaque list pointer offset: OPB size * tile index * 4 + +void region_array(volatile uint32_t * buf, + uint32_t ol_base, + const uint32_t width, // in tile units (1 tile unit = 32 pixels) + const uint32_t height) // in tile units (1 tile unit = 32 pixels) +{ + volatile region_array_entry * region_array = reinterpret_cast(buf); + uint32_t ix = 0; + + // create a "dummy region array [item]" for CORE & TA-related bug #21: + // "Misshapen tiles or missing tiles occur" + region_array[ix].tile = REGION_ARRAY__FLUSH_ACCUMULATE; + region_array[ix].opaque_list_pointer = REGION_ARRAY__LIST_POINTER__EMPTY; + region_array[ix].opaque_modifier_volume_list_pointer = REGION_ARRAY__LIST_POINTER__EMPTY; + region_array[ix].translucent_list_pointer = REGION_ARRAY__LIST_POINTER__EMPTY; + region_array[ix].translucent_modifier_volume_list_pointer = REGION_ARRAY__LIST_POINTER__EMPTY; + region_array[ix].punch_through_list_pointer = REGION_ARRAY__LIST_POINTER__EMPTY; + + ix += 1; + + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { + region_array[ix].tile = REGION_ARRAY__TILE_Y_POSITION(y) + | REGION_ARRAY__TILE_X_POSITION(x); + + uint32_t tile_index = y * width + x; + constexpr uint32_t opaque_list_opb_size = 16 * 4; // in bytes; this must match O_OPB in TA_ALLOC_CTRL + region_array[ix].opaque_list_pointer = ol_base + (opaque_list_opb_size * tile_index); + region_array[ix].opaque_modifier_volume_list_pointer = REGION_ARRAY__LIST_POINTER__EMPTY; + region_array[ix].translucent_list_pointer = REGION_ARRAY__LIST_POINTER__EMPTY; + region_array[ix].translucent_modifier_volume_list_pointer = REGION_ARRAY__LIST_POINTER__EMPTY; + region_array[ix].punch_through_list_pointer = REGION_ARRAY__LIST_POINTER__EMPTY; + + ix += 1; + } + } +} diff --git a/holly/region_array.h b/holly/region_array.h new file mode 100644 index 0000000..fd6a93b --- /dev/null +++ b/holly/region_array.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +void region_array(volatile uint32_t * buf, + uint32_t ol_base, + const uint32_t width, // in tile units (1 tile unit = 32 pixels) + const uint32_t height); // in tile units (1 tile unit = 32 pixels) diff --git a/holly/ta_parameter.cpp b/holly/ta_parameter.cpp new file mode 100644 index 0000000..e6d1701 --- /dev/null +++ b/holly/ta_parameter.cpp @@ -0,0 +1,185 @@ +#include +#include + +#include "float_uint32.h" +#include "ta_parameter.h" +#include "isp_tsp.h" + +static_assert((sizeof (float)) == (sizeof (uint32_t))); + +struct vertex_polygon_type_0 { + uint32_t parameter_control_word; + float x; + float y; + float z; + uint32_t _res0; + uint32_t _res1; + uint32_t base_color; + uint32_t _res2; +}; + +static_assert((sizeof (vertex_polygon_type_0)) == 32); +static_assert((offsetof (struct vertex_polygon_type_0, parameter_control_word)) == 0x00); +static_assert((offsetof (struct vertex_polygon_type_0, x)) == 0x04); +static_assert((offsetof (struct vertex_polygon_type_0, y)) == 0x08); +static_assert((offsetof (struct vertex_polygon_type_0, z)) == 0x0c); +static_assert((offsetof (struct vertex_polygon_type_0, _res0)) == 0x10); +static_assert((offsetof (struct vertex_polygon_type_0, _res1)) == 0x14); +static_assert((offsetof (struct vertex_polygon_type_0, base_color)) == 0x18); +static_assert((offsetof (struct vertex_polygon_type_0, _res2)) == 0x1c); + +struct global_polygon_type_0 { + uint32_t parameter_control_word; + uint32_t isp_tsp_instruction_word; + uint32_t tsp_instruction_word; + uint32_t texture_control_word; + uint32_t _res0; + uint32_t _res1; + uint32_t data_size_for_sort_dma; + uint32_t next_address_for_sort_dma; +}; + +static_assert((sizeof (global_polygon_type_0)) == 32); +static_assert((offsetof (struct global_polygon_type_0, parameter_control_word)) == 0x00); +static_assert((offsetof (struct global_polygon_type_0, isp_tsp_instruction_word)) == 0x04); +static_assert((offsetof (struct global_polygon_type_0, tsp_instruction_word)) == 0x08); +static_assert((offsetof (struct global_polygon_type_0, texture_control_word)) == 0x0c); +static_assert((offsetof (struct global_polygon_type_0, _res0)) == 0x10); +static_assert((offsetof (struct global_polygon_type_0, _res1)) == 0x14); +static_assert((offsetof (struct global_polygon_type_0, data_size_for_sort_dma)) == 0x18); +static_assert((offsetof (struct global_polygon_type_0, next_address_for_sort_dma)) == 0x1c); + +struct global_end_of_list { + uint32_t parameter_control_word; + uint32_t _res0; + uint32_t _res1; + uint32_t _res2; + uint32_t _res3; + uint32_t _res4; + uint32_t _res5; + uint32_t _res6; +}; + +static_assert((sizeof (global_end_of_list)) == 32); +static_assert((offsetof (struct global_end_of_list, parameter_control_word)) == 0x00); +static_assert((offsetof (struct global_end_of_list, _res0)) == 0x04); +static_assert((offsetof (struct global_end_of_list, _res1)) == 0x08); +static_assert((offsetof (struct global_end_of_list, _res2)) == 0x0c); +static_assert((offsetof (struct global_end_of_list, _res3)) == 0x10); +static_assert((offsetof (struct global_end_of_list, _res4)) == 0x14); +static_assert((offsetof (struct global_end_of_list, _res5)) == 0x18); +static_assert((offsetof (struct global_end_of_list, _res6)) == 0x1c); + +namespace para_control { + namespace para_type { + constexpr uint32_t end_of_list = 0 << 29; + constexpr uint32_t user_tile_clip = 1 << 29; + constexpr uint32_t object_list_set = 2 << 29; + constexpr uint32_t polygon_or_modifier_volume = 4 << 29; + constexpr uint32_t sprite = 5 << 29; + constexpr uint32_t vertex_parameter = 7 << 29; + } + + constexpr uint32_t end_of_strip = 1 << 28; + + namespace list_type { + constexpr uint32_t opaque = 0 << 24; + constexpr uint32_t opaque_modifier_volume = 1 << 24; + constexpr uint32_t translucent = 2 << 24; + constexpr uint32_t translucent_modifier_volume = 3 << 24; + constexpr uint32_t punch_through = 4 << 24; + } +} + +namespace group_control { + constexpr uint32_t group_en = 1 << 23; + + namespace strip_len { + constexpr uint32_t _1_strip = 0 << 18; + constexpr uint32_t _2_strip = 1 << 18; + constexpr uint32_t _4_strip = 2 << 18; + constexpr uint32_t _6_strip = 3 << 18; + } + + namespace user_clip { + constexpr uint32_t disabled = 0 << 16; + constexpr uint32_t inside_enable = 2 << 16; + constexpr uint32_t outside_enable = 3 << 16; + } +} + +namespace obj_control { + constexpr uint32_t shadow = 1 << 7; + constexpr uint32_t volume = 1 << 6; + + namespace col_type { + constexpr uint32_t packed_color = 0 << 4; + constexpr uint32_t floating_color = 1 << 4; + constexpr uint32_t intensity_mode_1 = 2 << 4; + constexpr uint32_t intensity_mode_2 = 3 << 4; + } + + constexpr uint32_t texture = 1 << 3; + constexpr uint32_t offset = 1 << 2; + constexpr uint32_t gouraud = 1 << 1; + constexpr uint32_t _16bit_uv = 1 << 0; +} + +void vertex(volatile uint32_t * buf, + const float x, + const float y, + const float z, + const uint32_t base_color, + bool end_of_strip + ) +{ + volatile vertex_polygon_type_0 * parameter = reinterpret_cast(buf); + + parameter->parameter_control_word = para_control::para_type::vertex_parameter; + + if (end_of_strip) + parameter->parameter_control_word |= para_control::end_of_strip; + + parameter->x = x; + parameter->y = y; + parameter->z = z; + parameter->_res0 = 0; + parameter->_res1 = 0; + parameter->base_color = base_color; + parameter->_res2 = 0; +} + +void triangle(volatile uint32_t * buf) +{ + volatile global_polygon_type_0 * parameter = reinterpret_cast(buf); + + parameter->parameter_control_word = para_control::para_type::polygon_or_modifier_volume + | para_control::list_type::opaque + | obj_control::col_type::packed_color; + + parameter->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::always + | isp_tsp_instruction_word::culling_mode::no_culling; + + parameter->tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog; + parameter->texture_control_word = 0; + parameter->_res0 = 0; + parameter->_res1 = 0; + parameter->data_size_for_sort_dma = 0; + parameter->next_address_for_sort_dma = 0; +} + +void end_of_list(volatile uint32_t * buf) +{ + volatile global_end_of_list * parameter = reinterpret_cast(buf); + + parameter->parameter_control_word = para_control::para_type::end_of_list; + parameter->_res0 = 0; + parameter->_res1 = 0; + parameter->_res2 = 0; + parameter->_res3 = 0; + parameter->_res4 = 0; + parameter->_res5 = 0; + parameter->_res6 = 0; +} diff --git a/holly/ta_parameter.h b/holly/ta_parameter.h new file mode 100644 index 0000000..213ade7 --- /dev/null +++ b/holly/ta_parameter.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +void vertex(volatile uint32_t * buf, + const float x, + const float y, + const float z, + const uint32_t base_color, + bool end_of_strip + ); + +void triangle(volatile uint32_t * buf); + +void end_of_list(volatile uint32_t * buf); diff --git a/load.c b/load.cpp similarity index 72% rename from load.c rename to load.cpp index d42846e..0184672 100644 --- a/load.c +++ b/load.cpp @@ -24,10 +24,10 @@ struct load_state { static struct load_state state; -void move(void *dest, const void *src, size_t n) +void move(void *dst, const void *src, size_t n) { - uint8_t *d = dest; - const uint8_t *s = src; + uint8_t * d = reinterpret_cast(dst); + const uint8_t * s = reinterpret_cast(src); if (d==s) return; if (d 0) { - SH7091.SCIF.SCFTDR2 = c; + uint8_t * dest = reinterpret_cast(state.addr2); + if (*size > 0) { + sh7091.SCIF.SCFTDR2 = c; // write c to dest *dest = c; @@ -109,19 +109,19 @@ void load_recv(uint8_t c) } return; break; - case CMD_JUMP: - // jump - state.len = 0; - state.command = CMD_NONE; - debug("prejump\n"); - HOLLY.VO_BORDER_COL = (31 << 11); - void (*fptr)(void) = (void (*)(void))state.addr1; - HOLLY.VO_BORDER_COL = (63 << 5) | (31 << 0); - fptr(); - debug("postjump\n"); - return; - break; } + case CMD_JUMP: + // jump + state.len = 0; + state.command = CMD_NONE; + debug("prejump\n"); + holly.VO_BORDER_COL = (31 << 11); + void (*fptr)(void) = (void (*)(void))state.addr1; + holly.VO_BORDER_COL = (63 << 5) | (31 << 0); + fptr(); + debug("postjump\n"); + return; + break; } } } diff --git a/main.c b/main.c deleted file mode 100644 index ec6aef2..0000000 --- a/main.c +++ /dev/null @@ -1,66 +0,0 @@ -#include - -#include "cache.h" -#include "load.h" - -#include "sh7091.h" -#include "sh7091_bits.h" - -extern uint32_t __bss_link_start __asm("__bss_link_start"); -extern uint32_t __bss_link_end __asm("__bss_link_end"); - -void serial() -{ - SH7091.SCIF.SCSCR2 = 0; - SH7091.SCIF.SCSMR2 = 0; - SH7091.SCIF.SCBRR2 = 12; - -#define SCFCR2__TFRST (1 << 2) -#define SCFCR2__RFRST (1 << 1) - SH7091.SCIF.SCFCR2 = SCFCR2__TFRST | SCFCR2__RFRST; - // tx/rx trigger on 1 byte - SH7091.SCIF.SCFCR2 = 0; - - SH7091.SCIF.SCSPTR2 = 0; - SH7091.SCIF.SCLSR2 = 0; - -#define SCSCR2__TE (1 << 5) -#define SCSCR2__RE (1 << 4) - SH7091.SCIF.SCSCR2 = SCSCR2__TE | SCSCR2__RE; -} - -void main() -{ - cache_init(); - - // clear BSS - uint32_t * start = &__bss_link_start; - uint32_t * end = &__bss_link_end; - while (start < end) { - *start++ = 0; - } - - serial(); - - load_init(); - - while (1) { -#define SCFSR2__ER (1 << 7) /* read error */ -#define SCFSR2__TEND (1 << 6) /* transmit end */ -#define SCFSR2__TFDE (1 << 5) /* transmit fifo data empty */ -#define SCFSR2__BRK (1 << 4) /* break detect */ -#define SCFSR2__FER (1 << 3) /* framing error */ -#define SCFSR2__PER (1 << 2) /* parity error */ -#define SCFSR2__RDF (1 << 1) /* receive FIFO data full */ -#define SCFSR2__DR (1 << 0) /* receive data ready */ - - while ((SH7091.SCIF.SCFSR2 & SCFSR2__RDF) == 0) { - // wait - } - while ((SH7091.SCIF.SCFDR2 & 0b11111) > 0) { - uint8_t c = SH7091.SCIF.SCFRDR2; - load_recv(c); - } - SH7091.SCIF.SCFSR2 = SH7091.SCIF.SCFSR2 & (~SCFSR2__RDF); - } -} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..c475e78 --- /dev/null +++ b/main.cpp @@ -0,0 +1,96 @@ +#include + +#include "cache.h" +#include "load.h" +#include "vga.h" + +#include "sh7091.h" +#include "sh7091_bits.h" +#include "memorymap.h" + +#include "rgb.h" +#include "scene.h" + +extern uint32_t __bss_link_start __asm("__bss_link_start"); +extern uint32_t __bss_link_end __asm("__bss_link_end"); + +void serial() +{ + sh7091.SCIF.SCSCR2 = 0; + sh7091.SCIF.SCSMR2 = 0; + sh7091.SCIF.SCBRR2 = 1; // 520833.3 + + sh7091.SCIF.SCFCR2 = SCFCR2__TFRST | SCFCR2__RFRST; + // tx/rx trigger on 1 byte + sh7091.SCIF.SCFCR2 = 0; + + sh7091.SCIF.SCSPTR2 = 0; + sh7091.SCIF.SCLSR2 = 0; + + sh7091.SCIF.SCSCR2 = SCSCR2__TE | SCSCR2__RE; +} + +extern "C" +void main() +{ + cache_init(); + + // clear BSS + uint32_t * start = &__bss_link_start; + uint32_t * end = &__bss_link_end; + while (start < end) { + *start++ = 0; + } + + serial(); + + vga(); + + v_sync_in(); + + volatile uint16_t * framebuffer = reinterpret_cast(&texture_memory[0]); + for (int y = 0; y < 480; y++) { + for (int x = 0; x < 640; x++) { + struct hsv hsv = {(y * 255) / 480, 255, 255}; + struct rgb rgb = hsv_to_rgb(hsv); + framebuffer[y * 640 + x] = ((rgb.r >> 3) << 11) | ((rgb.g >> 2) << 5) | ((rgb.b >> 3) << 0); + } + } + + + while (1) { + v_sync_in(); + scene_holly_init(); + scene_init_texture_memory(); + scene_ta_init(); + scene_geometry_transfer(); + scene_wait_opaque_list(); + scene_start_render(); + + // I do not understand why, but flycast does not show the first-rendered + // framebuffer. + v_sync_in(); + scene_ta_init(); + scene_geometry_transfer(); + scene_wait_opaque_list(); + scene_start_render(); + + // do nothing forever + while(1); + + /* + load_init(); + + while (1) { + while ((sh7091.SCIF.SCFSR2 & SCFSR2__RDF) == 0) { + // wait + } + while ((sh7091.SCIF.SCFDR2 & 0b11111) > 0) { + uint8_t c = sh7091.SCIF.SCFRDR2; + load_recv(c); + } + sh7091.SCIF.SCFSR2 = sh7091.SCIF.SCFSR2 & (~SCFSR2__RDF); + } +*/ + } +} diff --git a/main.lds b/main.lds index c7e294d..b64b5c5 100644 --- a/main.lds +++ b/main.lds @@ -64,11 +64,7 @@ SECTIONS __ctors_link_end = ADDR(.ctors) + SIZEOF(.ctors); } -SH7091_IC_A = 0xf0000000; -SH7091_IC_D = 0xf1000000; -SH7091_OC_A = 0xf4000000; -SH7091_OC_D = 0xf5000000; -SH7091 = 0xff000000; -HOLLY = 0xa05f8000; __p1ram_start = ORIGIN(p1ram); __p1ram_end = ORIGIN(p1ram) + LENGTH(p1ram); + +INCLUDE "addresses.lds" diff --git a/memorymap.h b/memorymap.h new file mode 100644 index 0000000..fed8642 --- /dev/null +++ b/memorymap.h @@ -0,0 +1,13 @@ +#include + +extern volatile uint32_t system_boot_rom[0x200000] __asm("system_boot_rom"); +extern volatile uint32_t aica_wave_memory[0x200000] __asm("aica_wave_memory"); +extern volatile uint32_t texture_memory[0x800000] __asm("texture_memory"); +extern volatile uint32_t system_memory[0x1000000] __asm("system_memory"); +extern volatile uint32_t ta_fifo_polygon_converter[0x800000] __asm("ta_fifo_polygon_converter"); +extern volatile uint32_t ta_fifo_yuv_converter[0x800000] __asm("ta_fifo_yuv_converter"); +extern volatile uint32_t ta_fifo_texture_memory[0x800000] __asm("ta_fifo_texture_memory"); +extern volatile uint32_t ta_fifo_polygon_converter_mirror[0x800000] __asm("ta_fifo_polygon_converter_mirror"); +extern volatile uint32_t ta_fifo_yuv_converter_mirror[0x800000] __asm("ta_fifo_yuv_converter_mirror"); +extern volatile uint32_t ta_fifo_texture_memory_mirror[0x800000] __asm("ta_fifo_texture_memory_mirror"); +extern volatile uint32_t store_queue[0x4000000] __asm("store_queue"); diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..5010466 --- /dev/null +++ b/notes.txt @@ -0,0 +1,176 @@ +======= KMYINIT.C ======== + +- ISP_FEED_CFG -- TRANSLUCENCY_CACHE_SIZE(0x200) + +- FPU_SHAD_SCALE -- SCALE_FACTOR(1) +- FPU_CULL_VAL -- 1.0f +- FPU_PERP_VAL -- 0.0f +- SPAN_SORT_CFG -- SPAN_SORT_ENABLE | OFFSET_SORT_ENABLE + +- FOG_COL_RAM -- RED(127) | GREEN(127) | BLUE(127) +- FOG_COL_VERT -- RED(127) | GREEN(127) | BLUE(127) +- FOG_CLAMP_MIN -- ALPHA(0) | RED(0) | GREEN(0) | BLUE(0) +- FOG_CLAMP_MAX -- ALPHA(255) | RED(255) | GREEN(255) | BLUE(255) + +- HALF_OFFSET -- TSP_TEXEL_SAMPLE_POSITION_CENTER + | TSP_PIXEL_SAMPLE_POSITION_CENTER + | FPU_PIXEL_SAMPLE_POSITION_CENTER + +- FPU_PARAM_CFG -- REGION_HEADER_TYPE__2 + | TSP_PARAMETER_BURST_TRIGGER(31) + | ISP_PARAMETER_BURST_TRIGGER(31) + | POINTER_BURST_SIZE(7) // must be less than OPB size + | POINTER_FIRST_BURST_SIZE(7) // half of pointer burst size(?) + +=========================== + +jargon: + +OPB - Object Pointer Block (a list of pointers to objects in the object list) +OL - Object list +ISP - Image Synthesis Processor +TSP - Texture Shading Processor + + +====== KMYSETUPTA.C ====== + +The OPB initial area size that is needed for one TA input list is the +product of the total OPB size for all lists specified by the +TA_ALLOC_CTRL register before the input of that list, multiplied by +the number of Tiles in the Global Tile Clip area that is specified by +the TA_GLOB_TILE_CLIP register. The amount of memory that should be +reserved in texture memory as the OPB initial area is the sum of the +OPB initial area size for the one list added together for each of the +polygon lists that are input to the TA. + +(OPB initial area size for one list) = {(Opaque list OPB size)} * (Number of Tiles in Global Tile Clip area) x 4 bytes + + 4byte + + +- TA_GLOB_TILE_CLIP -- TILE_Y_NUM(480 / 32) + | TILE_X_NUM(640 / 32) + (removes polygons that are entirely outside the clip area) + +- TA_ALLOC_CTRL -- TA_ALLOC_CTRL__OPB_MODE_INCREASING + | TA_ALLOC_CTRL__PT_OPB__NONE + | TA_ALLOC_CTRL__TM_OPB__NONE + | TA_ALLOC_CTRL__T_OPB__NONE + | TA_ALLOC_CTRL__OM_OPB__NONE + | TA_ALLOC_CTRL__O_OPB__16 + +- TA_ISP_BASE -- + (Base address for ISP/TSP parameters (relative address from the start of texture memory) (read by PARAM_BASE)) + +- TA_OL_LIMIT - The last address of the object list (relative address from the start of texture memory) +- TA_PARAM_LIMIT aka TA_ISP_LIMIT - The last address of the ISP/TSP parameter list (relative address from the start of texture memory) +- TA_NEXT_OPB_INIT - the address for storing additional OPBs during list initialization (relative address from the start of texture memory) + +The value in the TA_NEXT_OPB_INIT register, which should be set prior +to list initialization, is the sum starting address value of the +Object List that is stored in texture memory and the OPB initial area +size. + +(TA_NEXT_OPB_INIT register value) = (TA_OL_BASE register value at list initialization) + (OPB initial area size at list initialization) +-- &object_list[0] + (sizeof (mem->object_list)) +(end of object_list) + +- SOFTRESET = SOFTRESET__TA_SOFT_RESET +- SOFTRESET = 0 + +- TA_GLOB_TILE_CLIP = TILE_Y_NUM(15) + | TILE_X_NUM(20) // 640x480 + +- TA_ISP_BASE = 0x00000000 +- TA_ISP_LIMIT = 0x00100000 +- TA_OL_BASE = 0x00100000 +- TA_OL_LIMIT = 0x00200000 +- TA_NEXT_OPB_INIT = 0x00100000 + +- TA_LIST_INIT +- dummy read on TA_LIST_INIT + +==== BGPLANE ==== + +KMYSETPASSDEPTH.C +KMYINITIALIZEBGPLANE.C + +[ 0] = DEPTH_COMPARE_MODE(GREATER) +[ 1] = SRC_ALPHA(One) + | FOG_CONTROL(NO_FOG) +[ 2] = 0 + +[ 3] = 0.0 // x +[ 4] = 0.0 // y +[ 5] = 1.f/100000 // 1/z +[ 6] = 0x0FF000000 // color + +[ 7] = 0.0 // x +[ 8] = 0.0 // y +[ 9] = 1.f/100000 // 1/z +[10] = 0x0FF000000 // color + +[11] = 0.0 // x +[12] = 0.0 // y +[13] = 1.f/100000 // 1/z +[14] = 0x0FF000000 // color + +==== KMYSTARTRENDER.C ==== + +region array diagram on page 179 + +REGION_BASE the region array is not generated by the TA +PARAM_BASE -- equal to TA_ISP_BASE +FB_W_CTRL -- FB_PACKMODE__565_RGB + +ISP_BACKGND_T // address of ISP/TSP parameters, without using TA + -- TAG_ADDRESS(0x00200000/4) + -- TAG_OFFSET(0) + -- SKIP(1) + +ISP_BACKGND_D -- Z coordinate ; 0x03727c5ac (1.f/100000) + +FB_W_LINESTRIDE -- 640 / 8; +FB_W_SOF1 -- framebuffer address +STARTRENDER + +==== store queue ==== + +store queue write: 0xe000_0000 - 0xe3ff_fffc + +write vertex list to: 0x10000000 + +TA: +"List of Input Parameters" + +page 201 (TA) Parameter Format + +control: + - End of List 0x0000_0000 + - User Tile Clip 0x2000_0000 + - Object List Set 0x4000_0000 + +page 196 Parameter Control Word + +page 222 ISP/TSP Instruction Word + +page 226 TSP Instruction Word + +page 233 Texture Control Word + + +Volume 0 + +Col_Type + 0 Packed (32-bit ARGB) + 1 Floating (32-bit float * 4) + +Textured 0 +Offset 0 +Gouraud 0 +16bit_UV 0 + +Global: Polygon Type 0 +Vertex: Polygon Type 0 + +- x y are 32-bit floating point in screen space +- z specified as reciprocal 1/z diff --git a/regs/gen/generate.py b/regs/gen/generate.py index 8ac8aa4..cf56c82 100644 --- a/regs/gen/generate.py +++ b/regs/gen/generate.py @@ -1,5 +1,12 @@ import io +def should_autonewline(line): + return ( + "static_assert" not in line + and "extern" not in line + and line.split()[1] != '=' # hacky; meh + ) + def _render(out, lines): indent = " " level = 0 @@ -14,7 +21,7 @@ def _render(out, lines): level += 2 if level == 0 and l and l[-1] == ";": - if "static_assert" not in l: + if should_autonewline(l): out.write("\n") return out diff --git a/regs/gen/holly.py b/regs/gen/holly.py index 7aad1aa..91cb896 100644 --- a/regs/gen/holly.py +++ b/regs/gen/holly.py @@ -6,7 +6,7 @@ from sh7091 import headers from generate import renderer def block(): - yield 'extern struct holly_reg HOLLY __asm("HOLLY");' + yield 'extern struct holly_reg holly __asm("holly");' input_file = sys.argv[1] rows = read_input(input_file) diff --git a/regs/gen/memorymap.py b/regs/gen/memorymap.py new file mode 100644 index 0000000..8540835 --- /dev/null +++ b/regs/gen/memorymap.py @@ -0,0 +1,42 @@ +import sys + +from sh7091 import read_input + +from generate import renderer + +def includes(): + yield "#include " + yield "" + +def process_rows(rows): + for row in rows: + name = row["name"].strip() + if not name: + continue + start = int(row["start"].strip(), 16) + size = row["size"].strip() + assert size.endswith("MB"), size + size = int(size.rstrip("MB")) + yield name, start, size * 1024 * 1024 + +def header(processed): + for name, _, size in processed: + yield f"extern volatile uint32_t {name}[0x{size:x}] __asm(\"{name}\");" + +def lds(processed): + for name, address, size in processed: + if address < 0x1000_0000: + address = address | 0xa000_0000 + yield f"{name} = 0x{address:08x};" + +input_file = sys.argv[1] +rows = read_input(input_file) +processed = list(process_rows(rows)) +render, out = renderer() +render(includes()) +render(header(processed)) +sys.stdout.write(out.getvalue()) + +render, out = renderer() +render(lds(processed)) +sys.stderr.write(out.getvalue()) diff --git a/regs/gen/sh7091.py b/regs/gen/sh7091.py index 9ba8826..d6b0722 100644 --- a/regs/gen/sh7091.py +++ b/regs/gen/sh7091.py @@ -142,7 +142,7 @@ def blocks(rows): yield f"static_assert((offsetof (struct sh7091_reg, {block})) == {hex(offset << 16)});" yield "" - yield 'extern struct sh7091_reg SH7091 __asm("SH7091");' + yield 'extern struct sh7091_reg sh7091 __asm("sh7091");' def headers(): yield "#include " diff --git a/regs/memorymap.csv b/regs/memorymap.csv new file mode 100644 index 0000000..89fb7e7 --- /dev/null +++ b/regs/memorymap.csv @@ -0,0 +1,43 @@ +"area","start","end","function","size","access","name" +"0","0x00000000","0x001FFFFF","System/Boot ROM","2MB",,"system_boot_rom" +"0","0x00200000","0x0021FFFF","Flash Memory","128KB",, +"0","0x00400000","0x005F67FF","Unassigned","-",, +"0","0x005F6800","0x005F69FF","System Control Registers","512B",, +"0","0x005F6C00","0x005F6CFF","Maple interface Control Registers","256B",, +"0","0x005F7000","0x005F70FF","GD-ROM","256B",, +"0","0x005F7400","0x005F74FF","G1 interface Control Registers","256B",, +"0","0x005F7800","0x005F78FF","G2 interface Control Registers","256B",, +"0","0x005F7C00","0x005F7CFF","PVR interface Control Registers","256B",, +"0","0x005F8000","0x005F9FFF","TA / PVR Core Registers","8KB",, +"0","0x00600000","0x006007FF","MODEM","2KB",, +"0","0x00600800","0x006FFFFF","G2","-",, +"0","0x00700000","0x00707FFF","AICA- Sound Control Registers","32KB",, +"0","0x00710000","0x0071000B","AICA- RTC Control Registers","12B",, +"0","0x00800000","0x00FFFFFF","AICA- Wave Memory","2MB",,"aica_wave_memory" +"0","0x01000000","0x01FFFFFF","External Device","16MB",, +"0","0x02000000","0x03FFFFFF","Mirror of 0x00000000 - 0x1FFFFFFF","32MB",, +,,,,,, +"1","0x04000000","0x04FFFFFF","Texture memory 64bit access","8MB",, +"1","0x05000000","0x05FFFFFF","Texture memory 32bit access","8MB",,"texture_memory" +"1","0x06000000","0x06FFFFFF","Mirror of 0x04000000 - 0x06FFFFFF","32MB",, +,,,,,, +"2","0x08000000","0x0bffffff","Unassigned","-",, +,,,,,, +"3","0x0c000000","0x0cffffff","System memory","16MB",,"system_memory" +"3","0x0d000000","0x0dffffff","Mirror of 0x0c000000 - 0x0cffffff","16MB",, +"3","0x0e000000","0x0fffffff","Mirror of 0x0c000000 - 0x0dffffff","32MB",, +,,,,,, +"4","0x10000000","0x107FFFFF","TA FIFO Polygon Converter","8MB",,"ta_fifo_polygon_converter" +"4","0x10800000","0x10FFFFFF","TA FIFO YUV Converter","8MB",,"ta_fifo_yuv_converter" +"4","0x11000000","0x11FFFFFF","TA FIFO Texture memory","8MB",,"ta_fifo_texture_memory" +"4","0x12000000","0x127FFFFF","Mirror of TA FIFO Polygon Converter","8MB",,"ta_fifo_polygon_converter_mirror" +"4","0x12800000","0x137FFFFF","Mirror of TA FIFO YUV Converter","8MB",,"ta_fifo_yuv_converter_mirror" +"4","0x13000000","0x13FFFFFF","Mirror of TA FIFO Texture memory","8MB",,"ta_fifo_texture_memory_mirror" +,,,,,, +"5","0x14000000","0x17FFFFFF","External device","64MB",, +,,,,,, +"6","0x18000000","0x1BFFFFFF","Unassigned","-",, +,,,,,, +"7","0x1C000000","0x1FFFFFFF","SH4 Internal area","-",, +,,,,,, +,"0xE0000000","0xE3FFFFFF","Store Queue","64MB",,"store_queue" diff --git a/regs/memorymap.ods b/regs/memorymap.ods index 3696344e4e1f2d9e9935c81b1372eb027f15a7b3..373544aa3218f24601b995cb6c8c68045501afd9 100644 GIT binary patch delta 15113 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 delta 14811 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 HFZKTeW^Y`v diff --git a/rgb.c b/rgb.cpp similarity index 100% rename from rgb.c rename to rgb.cpp diff --git a/scene.cpp b/scene.cpp new file mode 100644 index 0000000..f452764 --- /dev/null +++ b/scene.cpp @@ -0,0 +1,153 @@ +#include + +#include "holly/region_array.h" +#include "holly/background.h" +#include "holly/ta_parameter.h" +#include "holly.h" +#include "ta.h" + +#include "memorymap.h" +#include "storequeue.h" +#include "systembus.h" +#include "holly/float_uint32.h" + +struct texture_memory_alloc { + uint32_t isp_tsp_parameters[0x00100000 / 4]; // TA_ISP_BASE / PARAM_BASE (the actual objects) + uint32_t object_list[0x00100000 / 4]; // TA_OL_BASE (contains object pointer blocks) + uint32_t _res0[ 0x20 / 4]; // (the TA may clobber 4 bytes starting at TA_OL_LIMIT) + uint32_t region_array[0x00002000 / 4]; // REGION_BASE + uint32_t background[0x00000020 / 4]; // ISP_BACKGND_T + uint32_t framebuffer[0x00096000 / 4]; // FB_R_SOF1 / FB_W_SOF1 +}; + +float scene_triangle[3][3] = { + { 320.f, 120.f, 1/10.f}, + { 440.f, 360.f, 1/10.f}, + { 200.f, 360.f, 1/10.f}, +}; + +void scene_holly_init() +{ + holly.ISP_FEED_CFG = ISP_FEED_CFG__TRANSLUCENCY_CACHE_SIZE(0x200); + + holly.FPU_SHAD_SCALE = FPU_SHAD_SCALE__SCALE_FACTOR(1); + holly.FPU_CULL_VAL = _i(1.f); + holly.FPU_PERP_VAL = _i(0.f); + holly.SPAN_SORT_CFG = SPAN_SORT_CFG__SPAN_SORT_ENABLE + | SPAN_SORT_CFG__OFFSET_SORT_ENABLE; + + holly.FOG_COL_RAM = FOG_COL_RAM__RED(127) + | FOG_COL_RAM__GREEN(127) + | FOG_COL_RAM__BLUE(127); + + holly.FOG_COL_VERT = FOG_COL_VERT__RED(127) + | FOG_COL_VERT__GREEN(127) + | FOG_COL_VERT__BLUE(127); + + holly.FOG_CLAMP_MIN = FOG_CLAMP_MIN__ALPHA(0) + | FOG_CLAMP_MIN__RED(0) + | FOG_CLAMP_MIN__GREEN(0) + | FOG_CLAMP_MIN__BLUE(0); + + holly.FOG_CLAMP_MAX = FOG_CLAMP_MAX__ALPHA(255) + | FOG_CLAMP_MAX__RED(255) + | FOG_CLAMP_MAX__GREEN(255) + | FOG_CLAMP_MAX__BLUE(255); + + holly.HALF_OFFSET = HALF_OFFSET__TSP_TEXEL_SAMPLE_POSITION_CENTER + | HALF_OFFSET__TSP_PIXEL_SAMPLE_POSITION_CENTER + | HALF_OFFSET__FPU_PIXEL_SAMPLE_POSITION_CENTER; + + holly.FPU_PARAM_CFG = FPU_PARAM_CFG__REGION_HEADER_TYPE__2 + | FPU_PARAM_CFG__TSP_PARAMETER_BURST_TRIGGER(31) + | FPU_PARAM_CFG__ISP_PARAMETER_BURST_TRIGGER(31) + | FPU_PARAM_CFG__POINTER_BURST_SIZE(7) // must be less than OPB size + | FPU_PARAM_CFG__POINTER_FIRST_BURST_SIZE(7); // half of pointer burst size(?) +} + +void scene_ta_init() +{ + holly.SOFTRESET = SOFTRESET__TA_SOFT_RESET; + holly.SOFTRESET = 0; + + holly.TA_GLOB_TILE_CLIP = TA_GLOB_TILE_CLIP__TILE_Y_NUM((480 / 32) - 1) + | TA_GLOB_TILE_CLIP__TILE_X_NUM((640 / 32) - 1); + + holly.TA_ALLOC_CTRL = TA_ALLOC_CTRL__OPB_MODE_INCREASING + | TA_ALLOC_CTRL__PT_OPB__NONE + | TA_ALLOC_CTRL__TM_OPB__NONE + | TA_ALLOC_CTRL__T_OPB__NONE + | TA_ALLOC_CTRL__OM_OPB__NONE + | TA_ALLOC_CTRL__O_OPB__16; + + holly.TA_ISP_BASE = (offsetof (struct texture_memory_alloc, isp_tsp_parameters)); + holly.TA_ISP_LIMIT = (offsetof (struct texture_memory_alloc, object_list)); // the end of isp_tsp_parameters + holly.TA_OL_BASE = (offsetof (struct texture_memory_alloc, object_list)); + holly.TA_OL_LIMIT = (offsetof (struct texture_memory_alloc, region_array)); // the end of the object_list + holly.TA_NEXT_OPB_INIT = (offsetof (struct texture_memory_alloc, object_list)) + + (640 / 32) * (320 / 32) * 16 * 4; + + holly.TA_LIST_INIT = TA_LIST_INIT__LIST_INIT; + + volatile uint32_t _dummy_read = holly.TA_LIST_INIT; + (void)_dummy_read; +} + +void scene_wait_opaque_list() +{ + while ((system.ISTNRM & SB_ISTNRM__TAEOINT) == 0); + + system.ISTNRM = SB_ISTNRM__TAEOINT; +} + +void scene_geometry_transfer() +{ + triangle(store_queue); + sq_transfer_32byte(ta_fifo_polygon_converter); + + for (int i = 0; i < 3; i++) { + bool end_of_strip = i == 2; + + vertex(store_queue, + scene_triangle[i][0], // x + scene_triangle[i][1], // y + scene_triangle[i][2], // z + 0xffff00ff, // base_color + end_of_strip); + + sq_transfer_32byte(ta_fifo_polygon_converter); + } + + end_of_list(store_queue); + sq_transfer_32byte(ta_fifo_polygon_converter); +} + +void scene_init_texture_memory() +{ + volatile texture_memory_alloc * mem = reinterpret_cast(texture_memory); + + background_parameter(mem->background); + region_array(mem->region_array, + (offsetof (struct texture_memory_alloc, object_list)), + 640 / 32, // width + 480 / 32 // height + ); +} + +void scene_start_render() +{ + holly.REGION_BASE = (offsetof (struct texture_memory_alloc, region_array)); + holly.PARAM_BASE = (offsetof (struct texture_memory_alloc, isp_tsp_parameters)); + + holly.ISP_BACKGND_T = ISP_BACKGND_T__TAG_ADDRESS((offsetof (struct texture_memory_alloc, background)) / 4) + | ISP_BACKGND_T__TAG_OFFSET(0) + | ISP_BACKGND_T__SKIP(1); + holly.ISP_BACKGND_D = _i(1.f/100000); + + holly.FB_W_CTRL = FB_W_CTRL__FB_PACKMODE__565_RGB; + holly.FB_W_LINESTRIDE = 640 / 8; + holly.FB_W_SOF1 = (offsetof (struct texture_memory_alloc, framebuffer)); + holly.FB_R_SOF1 = (offsetof (struct texture_memory_alloc, framebuffer)); + + holly.STARTRENDER = 1; +} diff --git a/scene.h b/scene.h new file mode 100644 index 0000000..ad77bce --- /dev/null +++ b/scene.h @@ -0,0 +1,8 @@ +#pragma once + +void scene_holly_init(); +void scene_ta_init(); +void scene_init_texture_memory(); +void scene_geometry_transfer(); +void scene_wait_opaque_list(); +void scene_start_render(); diff --git a/sh7091.h b/sh7091.h index c25da18..039e71a 100644 --- a/sh7091.h +++ b/sh7091.h @@ -374,5 +374,4 @@ static_assert((offsetof (struct sh7091_reg, SCI)) == 0xe00000); static_assert((offsetof (struct sh7091_reg, SCIF)) == 0xe80000); static_assert((offsetof (struct sh7091_reg, UDI)) == 0xf00000); -extern struct sh7091_reg SH7091 __asm("SH7091"); - +extern struct sh7091_reg sh7091 __asm("sh7091"); diff --git a/sh7091_bits.h b/sh7091_bits.h index f5b6d28..c0807d5 100644 --- a/sh7091_bits.h +++ b/sh7091_bits.h @@ -13,3 +13,18 @@ #define PDTRA__RESERVED (0b01 << 8) #define PDTRA__RGB (0b10 << 8) #define PDTRA__AV (0b11 << 8) + +#define SCFCR2__TFRST (1 << 2) +#define SCFCR2__RFRST (1 << 1) + +#define SCSCR2__TE (1 << 5) +#define SCSCR2__RE (1 << 4) + +#define SCFSR2__ER (1 << 7) /* read error */ +#define SCFSR2__TEND (1 << 6) /* transmit end */ +#define SCFSR2__TFDE (1 << 5) /* transmit fifo data empty */ +#define SCFSR2__BRK (1 << 4) /* break detect */ +#define SCFSR2__FER (1 << 3) /* framing error */ +#define SCFSR2__PER (1 << 2) /* parity error */ +#define SCFSR2__RDF (1 << 1) /* receive FIFO data full */ +#define SCFSR2__DR (1 << 0) /* receive data ready */ diff --git a/storequeue.cpp b/storequeue.cpp new file mode 100644 index 0000000..e3d05ed --- /dev/null +++ b/storequeue.cpp @@ -0,0 +1,34 @@ +#include "sh7091.h" +#include "memorymap.h" + +void sq_transfer_32byte(volatile void * dst) +{ + // dst typically 0x10000000 (ta polygon converter) + sh7091.CCN.QACR0 = ((reinterpret_cast(dst) >> 26) & 0b111) << 2; + + // start 32-byte transfer from store queue 0 (SQ0) to QACR0 + asm volatile ("pref @%0" + : // output + : "r" (&store_queue[0]) // input + ); +} + + +void sq_transfer_64byte(volatile void * dst) +{ + // dst typically 0x10000000 (ta polygon converter) + sh7091.CCN.QACR0 = ((reinterpret_cast(dst) >> 26) & 0b111) << 2; + sh7091.CCN.QACR1 = ((reinterpret_cast(dst) >> 26) & 0b111) << 2; + + // start 32-byte transfer from store queue 0 (SQ0) to QACR0 + asm volatile ("pref @%0" + : // output + : "r" (&store_queue[0]) // input + ); + + // start 32-byte transfer from store queue 1 (SQ1) to QACR1 + asm volatile ("pref @%0" + : // output + : "r" (&store_queue[8]) // input + ); +} diff --git a/storequeue.h b/storequeue.h new file mode 100644 index 0000000..aae33bb --- /dev/null +++ b/storequeue.h @@ -0,0 +1,4 @@ +#pragma once + +void sq_transfer_32byte(volatile void * dst); +void sq_transfer_64byte(volatile void * dst); diff --git a/systembus.h b/systembus.h index d1a5fac..4aad7ae 100644 --- a/systembus.h +++ b/systembus.h @@ -301,13 +301,12 @@ static_assert((offsetof (struct pvr_if_reg, PDSTAPD)) == 0xf0); static_assert((offsetof (struct pvr_if_reg, PDSTARD)) == 0xf4); static_assert((offsetof (struct pvr_if_reg, PDLEND)) == 0xf8); -extern struct system_reg SYSTEM __asm("SYSTEM"); +extern struct system_reg system __asm("system"); -extern struct maple_if_reg MAPLE_IF __asm("MAPLE_IF"); +extern struct maple_if_reg maple_if __asm("maple_if"); -extern struct g1_if_reg G1_IF __asm("G1_IF"); +extern struct g1_if_reg g1_if __asm("g1_if"); -extern struct g2_if_reg G2_IF __asm("G2_IF"); - -extern struct pvr_if_reg PVR_IF __asm("PVR_IF"); +extern struct g2_if_reg g2_if __asm("g2_if"); +extern struct pvr_if_reg pvr_if __asm("pvr_if"); diff --git a/ta.h b/ta.h new file mode 100644 index 0000000..43a6662 --- /dev/null +++ b/ta.h @@ -0,0 +1,88 @@ +#define ISP_FEED_CFG__TRANSLUCENCY_CACHE_SIZE(n) (((n) & 0x3ff) << 14) + +#define FPU_SHAD_SCALE__INTENSITY_SHADOW_ENABLE (1 << 8) +#define FPU_SHAD_SCALE__SCALE_FACTOR(n) (((n) & 0xff) << 0) + +#define FPU_CULL_VAL__COMPARISON_VALUE(n) (_u32(__builtin_fabsf(n))) + +#define FPU_PERP_VAL__COMPARISON_VALUE(n) (_u32(__builtin_fabsf(n))) + +#define SPAN_SORT_CFG__CACHE_BYPASS (1 << 16) +#define SPAN_SORT_CFG__OFFSET_SORT_ENABLE (1 << 8) +#define SPAN_SORT_CFG__SPAN_SORT_ENABLE (1 << 0) + +#define FOG_COL_RAM__RED(n) (((n) & 0xff) << 16) +#define FOG_COL_RAM__GREEN(n) (((n) & 0xff) << 8) +#define FOG_COL_RAM__BLUE(n) (((n) & 0xff) << 0) + +#define FOG_COL_VERT__RED(n) (((n) & 0xff) << 16) +#define FOG_COL_VERT__GREEN(n) (((n) & 0xff) << 8) +#define FOG_COL_VERT__BLUE(n) (((n) & 0xff) << 0) + +#define FOG_CLAMP_MIN__ALPHA(n) (((n) & 0xff) << 24) +#define FOG_CLAMP_MIN__RED(n) (((n) & 0xff) << 16) +#define FOG_CLAMP_MIN__GREEN(n) (((n) & 0xff) << 8) +#define FOG_CLAMP_MIN__BLUE(n) (((n) & 0xff) << 0) + +#define FOG_CLAMP_MAX__ALPHA(n) (((n) & 0xff) << 24) +#define FOG_CLAMP_MAX__RED(n) (((n) & 0xff) << 16) +#define FOG_CLAMP_MAX__GREEN(n) (((n) & 0xff) << 8) +#define FOG_CLAMP_MAX__BLUE(n) (((n) & 0xff) << 0) + +#define HALF_OFFSET__TSP_TEXEL_SAMPLE_POSITION_CENTER (1 << 2) +#define HALF_OFFSET__TSP_PIXEL_SAMPLE_POSITION_CENTER (1 << 1) +#define HALF_OFFSET__FPU_PIXEL_SAMPLE_POSITION_CENTER (1 << 0) + +#define FPU_PARAM_CFG__REGION_HEADER_TYPE__1 (0 << 21) +#define FPU_PARAM_CFG__REGION_HEADER_TYPE__2 (1 << 21) +#define FPU_PARAM_CFG__TSP_PARAMETER_BURST_TRIGGER(n) (((n) & 0x3f) << 14) +#define FPU_PARAM_CFG__ISP_PARAMETER_BURST_TRIGGER(n) (((n) & 0x3f) << 8) +#define FPU_PARAM_CFG__POINTER_BURST_SIZE(n) (((n) & 0xf) << 4) +#define FPU_PARAM_CFG__POINTER_FIRST_BURST_SIZE(n) (((n) & 0xf) << 0) + +// -------------------- + +#define TA_GLOB_TILE_CLIP__TILE_Y_NUM(n) (((n) & 0b1111) << 16) +#define TA_GLOB_TILE_CLIP__TILE_X_NUM(n) (((n) & 0b11111) << 0) + +#define TA_ALLOC_CTRL__OPB_MODE_INCREASING (0 << 20) +#define TA_ALLOC_CTRL__OPB_MODE_DECREASING (1 << 20) +#define TA_ALLOC_CTRL__PT_OPB__NONE (0b00 << 16) +#define TA_ALLOC_CTRL__PT_OPB__8 (0b01 << 16) +#define TA_ALLOC_CTRL__PT_OPB__16 (0b10 << 16) +#define TA_ALLOC_CTRL__PT_OPB__32 (0b11 << 16) +#define TA_ALLOC_CTRL__TM_OPB__NONE (0b00 << 12) +#define TA_ALLOC_CTRL__TM_OPB__8 (0b01 << 12) +#define TA_ALLOC_CTRL__TM_OPB__16 (0b10 << 12) +#define TA_ALLOC_CTRL__TM_OPB__32 (0b11 << 12) +#define TA_ALLOC_CTRL__T_OPB__NONE (0b00 << 8) +#define TA_ALLOC_CTRL__T_OPB__8 (0b01 << 8) +#define TA_ALLOC_CTRL__T_OPB__16 (0b10 << 8) +#define TA_ALLOC_CTRL__T_OPB__32 (0b11 << 8) +#define TA_ALLOC_CTRL__OM_OPB__NONE (0b00 << 4) +#define TA_ALLOC_CTRL__OM_OPB__8 (0b01 << 4) +#define TA_ALLOC_CTRL__OM_OPB__16 (0b10 << 4) +#define TA_ALLOC_CTRL__OM_OPB__32 (0b11 << 4) +#define TA_ALLOC_CTRL__O_OPB__NONE (0b00 << 0) +#define TA_ALLOC_CTRL__O_OPB__8 (0b01 << 0) +#define TA_ALLOC_CTRL__O_OPB__16 (0b10 << 0) +#define TA_ALLOC_CTRL__O_OPB__32 (0b11 << 0) + +#define SOFTRESET__SDRAM_IF_SOFT_RESET (1 << 2) +#define SOFTRESET__CORE_SOFT_RESET (1 << 1) +#define SOFTRESET__TA_SOFT_RESET (1 << 0) + +#define TA_LIST_INIT__LIST_INIT (1 << 31) + +#define SB_ISTNRM__TAEOINT (1 << 7) // End of Transferring interrupt : Opaque List + +#define FB_W_CTRL__FB_PACKMODE__565_RGB (1 << 0); + +#define ISP_BACKGND_T__CACHE_BYPASS (1 << 28) +#define ISP_BACKGND_T__SHADOW (1 << 27) +#define ISP_BACKGND_T__SKIP(n) (((n) & 0b111) << 24) +//#define ISP_BACKGND_T__TAG_ADDRESS(n) ((n) & 0xfffff8) +#define ISP_BACKGND_T__TAG_ADDRESS(n) (((n) & 0x3fffff) << 3) +#define ISP_BACKGND_T__TAG_OFFSET(n) (((n) & 0b111) << 0) + +// ---------- diff --git a/test.c b/test.c index 05f2a08..96b70a2 100644 --- a/test.c +++ b/test.c @@ -13,6 +13,8 @@ void *memcpy(void *restrict dest, const void *restrict src, size_t n) return dest; } +extern uint32_t _binary_DSC08384_data_start __asm("_binary_DSC08384_data_start"); + void start(void) { /* @@ -25,35 +27,35 @@ void start(void) G2_IF.G2APRO = 0x4659404f; */ - HOLLY.SOFTRESET = 0b111; - HOLLY.TEXT_CONTROL = 3; + holly.SOFTRESET = 0b111; + holly.TEXT_CONTROL = 3; uint16_t xsize = 640; uint16_t ysize = 480; uint16_t fb_xclip_min = 0; uint16_t fb_xclip_max = xsize-1; - HOLLY.FB_X_CLIP = (fb_xclip_max << 16) | (fb_xclip_min << 0); + holly.FB_X_CLIP = (fb_xclip_max << 16) | (fb_xclip_min << 0); uint16_t fb_yclip_min = 0; uint16_t fb_yclip_max = ysize-1; - HOLLY.FB_Y_CLIP = (fb_yclip_max << 16) | (fb_yclip_min << 0); + holly.FB_Y_CLIP = (fb_yclip_max << 16) | (fb_yclip_min << 0); uint32_t fb_xsize = (xsize * 16)/(32) - 1; uint32_t fb_ysize = ysize - 3; uint32_t fb_mod = 1; - HOLLY.FB_R_SIZE = 0 + holly.FB_R_SIZE = 0 | (fb_xsize << 0) | (fb_ysize << 10) | (fb_mod << 20); uint32_t fb_linestride = (xsize * 16) / 64; - HOLLY.FB_W_LINESTRIDE = fb_linestride; + holly.FB_W_LINESTRIDE = fb_linestride; - HOLLY.FB_W_CTRL = 0 + holly.FB_W_CTRL = 0 | 0b001 << 0 // fb_packmode: RGB 565 ; - HOLLY.FB_R_CTRL = 0 + holly.FB_R_CTRL = 0 | 1 << 23 // vclk_div | 0 << 22 // fb_strip_buf_en | 0 << 16 // fb_strip_size @@ -64,43 +66,43 @@ void start(void) | 1 << 0 // fb_enable ; - HOLLY.FB_W_SOF1 = 0; - HOLLY.FB_W_SOF2 = 0; - HOLLY.FB_R_SOF1 = 0; - HOLLY.FB_R_SOF2 = 0; + holly.FB_W_SOF1 = 0; + holly.FB_W_SOF2 = 0; + holly.FB_R_SOF1 = 0; + holly.FB_R_SOF2 = 0; - HOLLY.VO_BORDER_COL = (31 << 11) | (31 << 0); + holly.VO_BORDER_COL = (31 << 11) | (31 << 0); uint16_t startx = 0x0a8; uint16_t starty = 0x028; - HOLLY.VO_STARTX = startx; - HOLLY.VO_STARTY = (starty << 16) | (starty << 0); + holly.VO_STARTX = startx; + holly.VO_STARTY = (starty << 16) | (starty << 0); - HOLLY.SPG_CONTROL = (1 << 8); // sync_direction__output ; non-default + holly.SPG_CONTROL = (1 << 8); // sync_direction__output ; non-default uint16_t hbstart = 0x0345; // default uint16_t hbend = 0x007e; // default - HOLLY.SPG_HBLANK = (hbend << 16) | (hbstart << 0); + holly.SPG_HBLANK = (hbend << 16) | (hbstart << 0); uint16_t hcount = 0x0359; // default uint16_t vcount = 0x020c; // non-default - HOLLY.SPG_LOAD = (vcount << 16) | (hcount << 0); + holly.SPG_LOAD = (vcount << 16) | (hcount << 0); uint16_t vbstart = 0x0208; // non-default uint16_t vbend = 0x0028; // non-default - HOLLY.SPG_VBLANK = (vbend << 16) | (vbstart << 0); + holly.SPG_VBLANK = (vbend << 16) | (vbstart << 0); uint16_t hswidth = 0x003f; uint16_t vswidth = 0x0003; uint16_t bpwidth = 0x0319; uint16_t eqwidth = 0x000f; - HOLLY.SPG_WIDTH = + holly.SPG_WIDTH = (hswidth << 0) | (vswidth << 8) | (bpwidth << 12) | (eqwidth << 22); - HOLLY.SPG_HBLANK_INT = hbstart << 16; + holly.SPG_HBLANK_INT = hbstart << 16; uint32_t hsync_pol = 0; uint32_t vsync_pol = 0; @@ -109,7 +111,7 @@ void start(void) uint32_t field_mode = 0; uint32_t pixel_double = 0; uint32_t pclk_delay = 0x16; - HOLLY.VO_CONTROL = 0 + holly.VO_CONTROL = 0 | (( pclk_delay & 0x3f) << 16 ) | (( pixel_double & 0x01) << 8 ) | (( field_mode & 0x0f) << 4 ) @@ -119,7 +121,9 @@ void start(void) | (( hsync_pol & 0x01) << 0 ); - HOLLY.SOFTRESET = 0b000; + holly.SOFTRESET = 0b000; + + const uint8_t * buf = (const uint8_t *)&_binary_DSC08384_data_start; reg16 * vram = (reg16 *)0xa5000000; v_sync_in(); @@ -130,15 +134,22 @@ void start(void) struct rgb rgb = hsv_to_rgb(hsv); vram[y * 640 + x] = ((rgb.r >> 3) << 11) | ((rgb.g >> 2) << 5) | ((rgb.b >> 3) << 0); */ - vram[y * 640 + x] = 30 << 5 | 31 << 11; + //vram[y * 640 + x] = 30 << 5 | 31 << 11; + uint32_t ix = y * 640 + x; + uint8_t r = buf[ix * 3 + 0]; + uint8_t g = buf[ix * 3 + 1]; + uint8_t b = buf[ix * 3 + 2]; + vram[ix] = ((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3) << 0); } } + /* vram[0] = 0xffff; vram[10] = 0xffff; vram[639] = 0xffff; vram[307199 - (640 * 3) - 1] = 0xffff; vram[307199 - (640 * 2) - 10] = 31 << 11; vram[307199 - (640 * 1) - 1] = 0xffff; + */ //vram[307199 - (640 * 2) - 10] = 0xf0ff; //vram[307199 - 640 - 10] = 0xf0ff; //vram[307199 - 10] = 0xf0ff; diff --git a/vga.c b/vga.cpp similarity index 63% rename from vga.c rename to vga.cpp index a0d0308..b9646af 100644 --- a/vga.c +++ b/vga.cpp @@ -10,45 +10,45 @@ uint32_t get_cable_type() { /* set all pins to input */ - SH7091.BSC.PCTRA = 0; + sh7091.BSC.PCTRA = 0; /* get cable type from pins 9 + 8 */ - return SH7091.BSC.PDTRA & PDTRA__MASK; + return sh7091.BSC.PDTRA & PDTRA__MASK; } void vga1() { - uint32_t fb_r_ctrl = HOLLY.FB_R_CTRL; - HOLLY.FB_R_CTRL = fb_r_ctrl & ~(1 << 0); // fb_enable = 0 + uint32_t fb_r_ctrl = holly.FB_R_CTRL; + holly.FB_R_CTRL = fb_r_ctrl & ~(1 << 0); // fb_enable = 0 uint32_t blank_video = 1; - HOLLY.VO_CONTROL |= (blank_video << 3); // blank_video + holly.VO_CONTROL |= (blank_video << 3); // blank_video - HOLLY.FB_R_SIZE = 0; + holly.FB_R_SIZE = 0; uint32_t vblank_in = 0x0208; uint32_t vblank_out = 0x0015; - HOLLY.SPG_VBLANK_INT = (vblank_out << 16) | (vblank_in << 0); + holly.SPG_VBLANK_INT = (vblank_out << 16) | (vblank_in << 0); uint32_t sync_direction = 1; - HOLLY.SPG_CONTROL = (sync_direction << 8); + holly.SPG_CONTROL = (sync_direction << 8); uint32_t hbstart = 0x0345; // default uint32_t hbend = 0x007e; // default - HOLLY.SPG_HBLANK = (hbend << 16) | (hbstart << 0); + holly.SPG_HBLANK = (hbend << 16) | (hbstart << 0); uint32_t hcount = 0x0359; // default uint32_t vcount = 0x020c; // non-default - HOLLY.SPG_LOAD = (vcount << 16) | (hcount << 0); + holly.SPG_LOAD = (vcount << 16) | (hcount << 0); uint32_t vbstart = 0x0208; // non-default uint32_t vbend = 0x0028; // non-default - HOLLY.SPG_VBLANK = (vbend << 16) | (vbstart << 0); + holly.SPG_VBLANK = (vbend << 16) | (vbstart << 0); uint32_t hswidth = 0x003f; uint32_t vswidth = 0x0003; uint32_t bpwidth = 0x0319; uint32_t eqwidth = 0x000f; - HOLLY.SPG_WIDTH = + holly.SPG_WIDTH = (hswidth << 0) | (vswidth << 8) | (bpwidth << 12) @@ -56,54 +56,47 @@ void vga1() uint32_t startx = 0x0a8; uint32_t starty = 0x028; - HOLLY.VO_STARTX = startx; - HOLLY.VO_STARTY = (starty << 16) | (starty << 0); + holly.VO_STARTX = startx; + holly.VO_STARTY = (starty << 16) | (starty << 0); - HOLLY.SPG_HBLANK_INT = hbstart << 16; + holly.SPG_HBLANK_INT = hbstart << 16; } void vga2() { - HOLLY.FB_BURSTCTRL = 0x00093f39; + holly.FB_BURSTCTRL = 0x00093f39; uint32_t xsize = 640; uint32_t ysize = 480; uint32_t fb_xclip_min = 0; uint32_t fb_xclip_max = xsize-1; - HOLLY.FB_X_CLIP = (fb_xclip_max << 16) | (fb_xclip_min << 0); + holly.FB_X_CLIP = (fb_xclip_max << 16) | (fb_xclip_min << 0); uint32_t fb_yclip_min = 0; uint32_t fb_yclip_max = ysize-1; - HOLLY.FB_Y_CLIP = (fb_yclip_max << 16) | (fb_yclip_min << 0); + holly.FB_Y_CLIP = (fb_yclip_max << 16) | (fb_yclip_min << 0); uint32_t fb_xsize = (xsize * 16)/(32) - 1; uint32_t fb_ysize = ysize - 3; uint32_t fb_mod = 1; - HOLLY.FB_R_SIZE = 0 + holly.FB_R_SIZE = 0 | (fb_xsize << 0) | (fb_ysize << 10) | (fb_mod << 20); uint32_t coeff0 = 0x40; uint32_t coeff1 = 0x80; - HOLLY.Y_COEFF = (coeff1 << 8) | (coeff0 << 0); + holly.Y_COEFF = (coeff1 << 8) | (coeff0 << 0); uint32_t vscale_factor = 0x0400; - HOLLY.SCALER_CTL = (vscale_factor << 0); + holly.SCALER_CTL = (vscale_factor << 0); - uint32_t fb_linestride = (xsize * 16) / 64; - HOLLY.FB_W_LINESTRIDE = fb_linestride; + holly.FB_W_SOF1 = 0; + holly.FB_W_SOF2 = 0; + holly.FB_R_SOF1 = 0; + holly.FB_R_SOF2 = 0; - HOLLY.FB_W_CTRL = 0 - | 0b001 << 0 // fb_packmode: RGB 565 - ; - - HOLLY.FB_W_SOF1 = 0; - HOLLY.FB_W_SOF2 = 0; - HOLLY.FB_R_SOF1 = 0; - HOLLY.FB_R_SOF2 = 0; - - HOLLY.FB_R_CTRL = 0 + holly.FB_R_CTRL = 0 | 1 << 23 // vclk_div | 0 << 22 // fb_strip_buf_en | 0 << 16 // fb_strip_size @@ -121,7 +114,7 @@ void vga2() uint32_t field_mode = 0; uint32_t pixel_double = 0; uint32_t pclk_delay = 0x16; - HOLLY.VO_CONTROL = 0 + holly.VO_CONTROL = 0 | (( pclk_delay & 0x3f) << 16 ) | (( pixel_double & 0x01) << 8 ) | (( field_mode & 0x0f) << 4 ) @@ -136,10 +129,10 @@ void vga2() void v_sync_in() { #define V_SYNC (1<<13) - while (!(V_SYNC & HOLLY.SPG_STATUS)) { + while (!(V_SYNC & holly.SPG_STATUS)) { asm volatile ("nop"); } - while ((V_SYNC & HOLLY.SPG_STATUS)) { + while ((V_SYNC & holly.SPG_STATUS)) { asm volatile ("nop"); } #undef V_SYNC @@ -148,10 +141,10 @@ void v_sync_in() void v_sync_out() { #define V_SYNC (1<<13) - while ((V_SYNC & HOLLY.SPG_STATUS)) { + while ((V_SYNC & holly.SPG_STATUS)) { asm volatile ("nop"); } - while (!(V_SYNC & HOLLY.SPG_STATUS)) { + while (!(V_SYNC & holly.SPG_STATUS)) { asm volatile ("nop"); } #undef V_SYNC @@ -161,9 +154,9 @@ void vga() { get_cable_type(); - HOLLY.SOFTRESET = 0b111; - HOLLY.TEXT_CONTROL = 3; - HOLLY.FB_W_CTRL = 9; + holly.SOFTRESET = 0b111; + holly.TEXT_CONTROL = 3; + holly.FB_W_CTRL = 9; /* */ @@ -172,10 +165,10 @@ void vga() v_sync_in(); - HOLLY.VO_BORDER_COL = (63 << 5) | (31 << 0); - HOLLY.VO_CONTROL = 0x0016; + holly.VO_BORDER_COL = (63 << 5) | (31 << 0); + holly.VO_CONTROL = 0x0016; - HOLLY.SOFTRESET = 0b000; + holly.SOFTRESET = 0b000; } void fill_framebuffer()