From 59708cc3cff2d3a016c8aab1d789442e0354abd7 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Fri, 23 Feb 2024 23:47:43 +0800 Subject: [PATCH] examples: add software_ta --- common.mk | 8 +- dumps/square.py | 4 +- dumps/test.py | 17 ++- dumps/triangle.py | 2 +- example/example.mk | 18 ++- example/software_ta.cpp | 238 +++++++++++++++++++++++++++++++++++++ example/software_ta.hpp | 202 +++++++++++++++++++++++++++++++ holly/isp_tsp.hpp | 2 + holly/object_list_data.hpp | 53 +++++++++ libm.c | 92 ++++++++++++++ main.lds | 20 ++-- regs/gen/core_bits.py | 19 ++- regs/object_list.csv | 28 +++++ regs/object_list.ods | Bin 0 -> 15687 bytes 14 files changed, 679 insertions(+), 24 deletions(-) create mode 100644 example/software_ta.cpp create mode 100644 example/software_ta.hpp create mode 100644 holly/object_list_data.hpp create mode 100644 libm.c create mode 100644 regs/object_list.csv create mode 100644 regs/object_list.ods diff --git a/common.mk b/common.mk index 300b9c6..f108004 100644 --- a/common.mk +++ b/common.mk @@ -13,7 +13,7 @@ CARCH = -m4-single-only -ml CFLAGS += -falign-functions=4 -ffunction-sections -fdata-sections -fshort-enums -ffreestanding -nostdlib CFLAGS += -Wall -Werror -Wfatal-errors CFLAGS += -Wno-error=narrowing -Wno-error=unused-variable -Wno-error=array-bounds= -Wno-array-bounds -CFLAGS += -mfsca -funsafe-math-optimizations +CFLAGS += -mfsca -funsafe-math-optimizations -ffast-math CFLAGS += -I$(dir $(MAKEFILE_PATH)) DEPFLAGS = -MMD -E # --print-gc-sections @@ -102,7 +102,7 @@ audio.pcm: synth 1 sin 440 vol -10dB mv $@.raw $@ -1ST_READ.BIN: example/macaw_multipass.bin +1ST_READ.BIN: example/dump_object_list.bin ./scramble $< $@ %.iso: 1ST_READ.BIN ip.bin @@ -152,6 +152,9 @@ holly/ta_global_parameter.hpp: regs/global_parameter_format.csv regs/gen/ta_para holly/ta_vertex_parameter.hpp: regs/vertex_parameter_format.csv regs/gen/ta_parameter_format.py python regs/gen/ta_parameter_format.py $< ta_vertex_parameter > $@ +holly/object_list_data.hpp: regs/object_list.csv regs/gen/core_bits.py + python regs/gen/core_bits.py $< object_list_data > $@ + sh7091/sh7091.hpp: regs/sh7091.csv regs/gen/sh7091.py python regs/gen/sh7091.py $< > $@ @@ -163,6 +166,7 @@ clean: -regextype posix-egrep \ -regex '.*\.(iso|o|d|bin|elf|cue|gch)$$' \ -exec rm {} \; + rm 1ST_READ.BIN .SUFFIXES: .INTERMEDIATE: diff --git a/dumps/square.py b/dumps/square.py index 1e60b28..3c6b760 100644 --- a/dumps/square.py +++ b/dumps/square.py @@ -1217,7 +1217,7 @@ _object_list = """ 0xeeeeeeee 0xeeeeeeee 0xeeeeeeee 0xeeeeeeee """ -object_list = [int(i, 16) for i in s.strip().split()] +object_list = [int(i, 16) for i in _object_list.strip().split()] _isp_tsp = """ 0x80400000 0x20800000 0x00000000 0x43480000 @@ -1238,7 +1238,7 @@ _isp_tsp = """ 0xeeeeeeee 0xeeeeeeee 0xeeeeeeee 0xeeeeeeee """ -isp_tsp = [int(i, 16) for i in s.strip().split()] +isp_tsp = [int(i, 16) for i in _isp_tsp.strip().split()] isp_tsp_instruction_word = 0x80400000 # Depth Compare Greater | 16Bit UV diff --git a/dumps/test.py b/dumps/test.py index 2823db4..2bcc077 100644 --- a/dumps/test.py +++ b/dumps/test.py @@ -1,4 +1,19 @@ import square import triangle -triangle.isp +object_list_size = 16 + +for i in range((640 // 32) * (480 // 32)): + block_start = (i + 0) * object_list_size + block_end = (i + 1) * object_list_size + objects = square.object_list[block_start:block_end] + + stride = (640 // 32) + + tile_x = (i % stride) + tile_y = (i // stride) + + for j in range(16): + if objects[j] & (1 << 28) != 0: + break + print(f"tile {tile_x: 4} {tile_y: 4}: {hex(objects[j])}") diff --git a/dumps/triangle.py b/dumps/triangle.py index 755d3c2..68d3703 100644 --- a/dumps/triangle.py +++ b/dumps/triangle.py @@ -1236,7 +1236,7 @@ _isp_tsp = """ 0xeeeeeeee 0xeeeeeeee 0xeeeeeeee 0xeeeeeeee """ -isp_tsp = [int(i, 16) for i in s.strip().split()] +isp_tsp = [int(i, 16) for i in _isp_tsp.strip().split()] isp_tsp_instruction_word = 0x80000000 tsp_instruction_word = 0x20800000 diff --git a/example/example.mk b/example/example.mk index d033e7a..9162562 100644 --- a/example/example.mk +++ b/example/example.mk @@ -326,7 +326,21 @@ DUMP_OBJECT_LIST_OBJ = \ holly/region_array.o \ holly/background.o \ holly/ta_fifo_polygon_converter.o \ - sh7091/serial.o + sh7091/serial.o \ + libm.o -example/dump_object_list.elf: LDSCRIPT = $(LIB)/alt.lds +example/dump_object_list.elf: LDSCRIPT = $(LIB)/main.lds example/dump_object_list.elf: $(START_OBJ) $(DUMP_OBJECT_LIST_OBJ) + +SOFTWARE_TA_OBJ = \ + example/software_ta.o \ + vga.o \ + holly/core.o \ + holly/region_array.o \ + holly/background.o \ + holly/ta_fifo_polygon_converter.o \ + sh7091/serial.o \ + libm.o + +example/software_ta.elf: LDSCRIPT = $(LIB)/main.lds +example/software_ta.elf: $(START_OBJ) $(SOFTWARE_TA_OBJ) diff --git a/example/software_ta.cpp b/example/software_ta.cpp new file mode 100644 index 0000000..9c9e18c --- /dev/null +++ b/example/software_ta.cpp @@ -0,0 +1,238 @@ +#include + +#include "align.hpp" +#include "vga.hpp" + +#include "holly/texture_memory_alloc.hpp" +#include "holly/holly.hpp" +#include "holly/core.hpp" +#include "holly/core_bits.hpp" +#include "holly/ta_fifo_polygon_converter.hpp" +#include "holly/ta_parameter.hpp" +#include "holly/ta_global_parameter.hpp" +#include "holly/ta_vertex_parameter.hpp" +#include "holly/ta_bits.hpp" +#include "holly/isp_tsp.hpp" +#include "holly/region_array.hpp" +#include "holly/background.hpp" +#include "memorymap.hpp" + +#include "sh7091/serial.hpp" + +#include "macaw.hpp" +#include "software_ta.hpp" + +uint32_t transform(uint32_t * ta_parameter_buf, + const software_ta::quad& quad) +{ + auto parameter = ta_parameter_writer(ta_parameter_buf); + + const uint32_t parameter_control_word = para_control::para_type::sprite +//const uint32_t parameter_control_word = para_control::para_type::polygon_or_modifier_volume + | para_control::list_type::opaque + | obj_control::col_type::packed_color; + + const uint32_t isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::greater + | isp_tsp_instruction_word::culling_mode::no_culling; + + const uint32_t tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog; + + const uint32_t texture_control_word = 0; + + constexpr uint32_t base_color = 0xffff0000; + + parameter.append() = + ta_global_parameter::sprite(parameter_control_word, + isp_tsp_instruction_word, + tsp_instruction_word, + texture_control_word, + base_color, + 0, // offset_color + 0, // data_size_for_sort_dma + 0); // next_address_for_sort_dma + + parameter.append() = + ta_vertex_parameter::sprite_type_0(para_control::para_type::vertex_parameter, + quad.a.x, + quad.a.y, + 0.1f, + quad.b.x, + quad.b.y, + 0.1f, + quad.c.x, + quad.c.y, + 0.1f, + quad.d.x, + quad.d.y); + + parameter.append() = ta_global_parameter::end_of_list(para_control::para_type::end_of_list); + + return parameter.offset; +} + +void init_texture_memory(const struct opb_size& opb_size) +{ + auto mem = reinterpret_cast(texture_memory32); + + // zeroize + for (uint32_t i = 0; i < 0x00100000 / 4; i++) { + mem->object_list[i] = 0xeeeeeeee; + mem->isp_tsp_parameters[i] = 0xeeeeeeee; + } + + background_parameter(mem->background, 0xff222200); + + region_array2(mem->region_array, + (offsetof (struct texture_memory_alloc, object_list)), + 640 / 32, // width + 480 / 32, // height + opb_size + ); +} + +uint32_t _ta_parameter_buf[((32 * (32 + 2)) + 32) / 4]; + +union u32_u8 { + uint32_t u32; + uint8_t u8[4]; +}; +static_assert((sizeof (union u32_u8)) == 4); + +void dump() +{ + auto mem = reinterpret_cast(texture_memory32); + + constexpr uint32_t screen_ol_size = 8 * 4 * (640 / 32) * (480 / 32); + for (uint32_t i = 0; i < (screen_ol_size + 0x100) / 4; i++) { + union u32_u8 n; + n.u32 = mem->object_list[i]; + + if (((i * 4) & 0x1f) == 0) + serial::character('\n'); + //if (((i * 4) & 0x3f) == 0) + // serial::character('\n'); + + serial::integer(n.u32, ' '); + } + + serial::character('\n'); + serial::character('\n'); + serial::character('\n'); + + for (uint32_t i = 0; i < (0x100) / 4; i++) { + union u32_u8 n; + n.u32 = mem->isp_tsp_parameters[i]; + + if (((i * 4) & 0x1f) == 0) + serial::character('\n'); + + serial::integer(n.u32, ' '); + } +} + +constexpr float half_degree = 0.01745329f / 2.f; + +constexpr software_ta::point rotate(const software_ta::point& p, const float theta) +{ + float x = p.x; + float y = p.y; + float x1; + + x1 = x * __builtin_cosf(theta) - y * __builtin_sinf(theta); + y = x * __builtin_sinf(theta) + y * __builtin_cosf(theta); + x = x1; + + x *= 240.f; + y *= 240.f; + x += 320.f; + y += 240.f; + + return {x, y}; +} + +void main() +{ + vga(); + + // The address of `ta_parameter_buf` must be a multiple of 32 bytes. + // This is mandatory for ch2-dma to the ta fifo polygon converter. + uint32_t * ta_parameter_buf = align_32byte(_ta_parameter_buf); + + constexpr uint32_t ta_alloc = ta_alloc_ctrl::pt_opb::no_list + | ta_alloc_ctrl::tm_opb::no_list + | ta_alloc_ctrl::t_opb::no_list + | ta_alloc_ctrl::om_opb::no_list + | ta_alloc_ctrl::o_opb::_8x4byte; + + constexpr struct opb_size opb_size = { .opaque = 8 * 4 + , .opaque_modifier = 0 + , .translucent = 0 + , .translucent_modifier = 0 + , .punch_through = 0 + }; + + holly.SOFTRESET = softreset::pipeline_soft_reset + | softreset::ta_soft_reset; + holly.SOFTRESET = 0; + + core_init(); + init_texture_memory(opb_size); + + float theta = 0; + uint32_t frame_ix = 0; + constexpr uint32_t num_frames = 1; + + bool hardware_ta = false; + + constexpr software_ta::quad q = { + {-0.5f, 0.5f}, + {-0.5f, -0.5f}, + { 0.5f, -0.5f}, + { 0.5f, 0.5f}, + }; + + while (true) { + if ((frame_ix & 255) == 0) { + holly.SOFTRESET = softreset::pipeline_soft_reset; + holly.SOFTRESET = 0; + hardware_ta = !hardware_ta; + if (hardware_ta) + serial::string("now using hardware TA\n"); + else + serial::string("now using software TA\n"); + } + + software_ta::quad qq; + qq.a = rotate(q.a, theta); + qq.b = rotate(q.b, theta); + qq.c = rotate(q.c, theta); + qq.d = rotate(q.d, theta); + + if (hardware_ta) { + ta_polygon_converter_init(opb_size.total(), + ta_alloc, + 640 / 32, + 480 / 32); + uint32_t ta_parameter_size = transform(ta_parameter_buf, qq); + ta_polygon_converter_transfer(ta_parameter_buf, ta_parameter_size); + ta_wait_opaque_list(); + } else { + auto mem = reinterpret_cast(texture_memory32); + + software_ta::object_pointer_blocks<8>(&mem->object_list[0], qq); + software_ta::isp_tsp_parameters(&mem->isp_tsp_parameters[0], qq); + } + + core_start_render(frame_ix, num_frames); + core_wait_end_of_render_video(); + + while (!spg_status::vsync(holly.SPG_STATUS)); + core_flip(frame_ix, num_frames); + while (spg_status::vsync(holly.SPG_STATUS)); + + frame_ix += 1; + theta += half_degree; + } +} diff --git a/example/software_ta.hpp b/example/software_ta.hpp new file mode 100644 index 0000000..be71bf0 --- /dev/null +++ b/example/software_ta.hpp @@ -0,0 +1,202 @@ +#include + +#include "holly/object_list_data.hpp" +#include "holly/isp_tsp.hpp" + +namespace software_ta { + +constexpr uint32_t tile_size = 32; + +constexpr uint32_t framebuffer_width = 640; +constexpr uint32_t framebuffer_height = 480; + +constexpr uint32_t tile_width = framebuffer_width / tile_size; +constexpr uint32_t tile_height = framebuffer_height / tile_size; + +struct point { + float x; + float y; +}; + +struct quad { + point a; + point b; + point c; + point d; +}; + +struct bounding_box { + point min; + point max; + + bounding_box(const quad q) + { + min.x = (q.a.x < q.b.x) ? q.a.x : q.b.x; + min.x = (min.x < q.c.x) ? min.x : q.c.x; + min.x = (min.x < q.d.x) ? min.x : q.d.x; + + min.y = (q.a.y < q.b.y) ? q.a.y : q.b.y; + min.y = (min.y < q.c.y) ? min.y : q.c.y; + min.y = (min.y < q.d.y) ? min.y : q.d.y; + + max.x = (q.a.x > q.b.x) ? q.a.x : q.b.x; + max.x = (max.x > q.c.x) ? max.x : q.c.x; + max.x = (max.x > q.d.x) ? max.x : q.d.x; + + max.y = (q.a.y > q.b.y) ? q.a.y : q.b.y; + max.y = (max.y > q.c.y) ? max.y : q.c.y; + max.y = (max.y > q.d.y) ? max.y : q.d.y; + } +}; + +template +constexpr uint32_t clamp_int(const float x) +{ + const int32_t n = static_cast(x); + return (n < 0 ? 0 : (n >= max ? (max - 1) : x)); +} + +constexpr uint32_t tile_start_x(bounding_box bb) +{ + return clamp_int(__builtin_floorf(bb.min.x / tile_size)); +} + +constexpr uint32_t tile_start_y(bounding_box bb) +{ + return clamp_int(__builtin_floorf(bb.min.y / tile_size)); +} + +constexpr uint32_t tile_end_x(bounding_box bb) +{ + return clamp_int(__builtin_ceilf(bb.max.x / tile_size)); +} + +constexpr uint32_t tile_end_y(bounding_box bb) +{ + return clamp_int(__builtin_ceilf(bb.max.y / tile_size)); +} + +template +struct object_pointer_block { + uint32_t pointer[N]; +}; +static_assert((sizeof (object_pointer_block<8>)) == 32); + +template +void object_pointer_blocks(volatile uint32_t * mem, const quad& quad) +{ + auto block = reinterpret_cast *>(mem); + uint8_t tile_indices[tile_height][tile_width]; + for (uint32_t i = 0; i < tile_width * tile_height; i++) { + reinterpret_cast(tile_indices)[i] = 0; + } + + bounding_box bb(quad); + + for (uint32_t y = tile_start_y(bb); y < tile_end_y(bb); y++) { + for (uint32_t x = tile_start_x(bb); x < tile_end_x(bb); x++) { + uint8_t& ix = tile_indices[y][x]; + auto& opb = block[y * tile_width + x]; + opb.pointer[ix] = object_list_data::pointer_type::quad_array + | object_list_data::quad_array::number_of_quads(0) + | object_list_data::quad_array::skip(1) + | object_list_data::quad_array::start(0); + ix++; + } + } + + for (uint32_t y = 0; y < tile_height; y++) { + for (uint32_t x = 0; x < tile_width; x++) { + uint8_t& ix = tile_indices[y][x]; + auto& opb = block[y * tile_width + x]; + opb.pointer[ix] = object_list_data::pointer_type::object_pointer_block_link + | object_list_data::object_pointer_block_link::end_of_list; + ix++; + } + } +} + +struct __untextured_quad_vertex { + float x; + float y; + float z; + uint32_t color; +}; + +struct __untextured_quad { + uint32_t isp_tsp_instruction_word; + uint32_t tsp_instruction_word; + uint32_t texture_control_word; + __untextured_quad_vertex a; + __untextured_quad_vertex b; + __untextured_quad_vertex c; + __untextured_quad_vertex d; +}; + +void isp_tsp_parameters(volatile uint32_t * mem, const quad& quad) +{ + auto params = reinterpret_cast(mem); + params->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::greater + | isp_tsp_instruction_word::culling_mode::no_culling; + + params->tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog; + + params->texture_control_word = 0; + + params->a.x = quad.a.x; + params->a.y = quad.a.y; + params->a.z = 0.1f; + params->b.color = 0; // invalid + + params->b.x = quad.b.x; + params->b.y = quad.b.y; + params->b.z = 0.1f; + params->b.color = 0; // invalid + + params->c.x = quad.c.x; + params->c.y = quad.c.y; + params->c.z = 0.1f; + params->c.color = 0xffff0000; + + params->d.x = quad.d.x; + params->d.y = quad.d.y; + params->d.z = 0.f; // invalid + params->b.color = 0; // invalid +} + +} + +/* +int main() +{ + using namespace software_ta; + + quad q = { + {200.f, 360.f}, + {200.f, 120.f}, + {440.f, 120.f}, + {440.f, 360.f}, + }; + + uint32_t opb_mem[300 * 8]; + + object_pointer_blocks<8>(opb_mem, q); + for (int i = 0; i < 300 * 8; i++) { + if ((i & 7) == 0) + std::cout << '\n' << std::dec << i / 8 << ' '; + std::cout << std::hex << opb_mem[i] << ' '; + } + std::cout << '\n'; + + uint32_t param_mem[32]; + for (int i = 0; i < 32; i++) { + param_mem[i] = 0xeeeeeeee; + } + isp_tsp_parameters(param_mem, q); + for (int i = 0; i < 32; i++) { + std::cout << std::hex << param_mem[i] << '\n'; + } +} +*/ diff --git a/holly/isp_tsp.hpp b/holly/isp_tsp.hpp index 9886b30..4f6ae1a 100644 --- a/holly/isp_tsp.hpp +++ b/holly/isp_tsp.hpp @@ -1,3 +1,5 @@ +#pragma once + #include namespace isp_tsp_instruction_word { diff --git a/holly/object_list_data.hpp b/holly/object_list_data.hpp new file mode 100644 index 0000000..c6227ab --- /dev/null +++ b/holly/object_list_data.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include "../float_uint32.hpp" + +namespace object_list_data { + namespace pointer_type { + constexpr uint32_t triangle_strip = 0b000 << 29; + constexpr uint32_t triangle_array = 0b100 << 29; + constexpr uint32_t quad_array = 0b101 << 29; + constexpr uint32_t object_pointer_block_link = 0b111 << 29; + + constexpr uint32_t bit_mask = 0x7 << 29; + } + + namespace triangle_strip { + namespace mask { + constexpr uint32_t t0 = 0b100000 << 25; + constexpr uint32_t t1 = 0b010000 << 25; + constexpr uint32_t t2 = 0b001000 << 25; + constexpr uint32_t t3 = 0b000100 << 25; + constexpr uint32_t t4 = 0b000010 << 25; + constexpr uint32_t t5 = 0b000001 << 25; + + constexpr uint32_t bit_mask = 0x3f << 25; + } + + constexpr uint32_t shadow = 1 << 24; + constexpr uint32_t skip(uint32_t num) { return (num & 0x7) << 21; } + constexpr uint32_t start(uint32_t num) { return (num & 0x1fffff) << 0; } + } + + namespace triangle_array { + constexpr uint32_t number_of_triangles(uint32_t num) { return (num & 0xf) << 25; } + constexpr uint32_t shadow = 1 << 24; + constexpr uint32_t skip(uint32_t num) { return (num & 0x7) << 21; } + constexpr uint32_t start(uint32_t num) { return (num & 0x1fffff) << 0; } + } + + namespace quad_array { + constexpr uint32_t number_of_quads(uint32_t num) { return (num & 0xf) << 25; } + constexpr uint32_t shadow = 1 << 24; + constexpr uint32_t skip(uint32_t num) { return (num & 0x7) << 21; } + constexpr uint32_t start(uint32_t num) { return (num & 0x1fffff) << 0; } + } + + namespace object_pointer_block_link { + constexpr uint32_t end_of_list = 1 << 28; + constexpr uint32_t next_pointer_block(uint32_t num) { return (num & 0xfffffc) << 0; } + } + +} diff --git a/libm.c b/libm.c new file mode 100644 index 0000000..9025c39 --- /dev/null +++ b/libm.c @@ -0,0 +1,92 @@ +#include + +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" + +/* +musl as a whole is licensed under the following standard MIT license: + +---------------------------------------------------------------------- +Copyright © 2005-2020 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------------------------------------------------------------- +*/ + +static inline void fp_force_evalf(float x) +{ + volatile float y; + y = x; +} + +#define FORCE_EVAL(x) do { \ + fp_force_evalf(x); \ +} while(0) + +float floorf(float x) +{ + union {float f; uint32_t i;} u = {x}; + int e = (int)(u.i >> 23 & 0xff) - 0x7f; + uint32_t m; + + if (e >= 23) + return x; + if (e >= 0) { + m = 0x007fffff >> e; + if ((u.i & m) == 0) + return x; + FORCE_EVAL(x + 0x1p120f); + if (u.i >> 31) + u.i += m; + u.i &= ~m; + } else { + FORCE_EVAL(x + 0x1p120f); + if (u.i >> 31 == 0) + u.i = 0; + else if (u.i << 1) + u.f = -1.0; + } + return u.f; +} + +float ceilf(float x) +{ + union {float f; uint32_t i;} u = {x}; + int e = (int)(u.i >> 23 & 0xff) - 0x7f; + uint32_t m; + + if (e >= 23) + return x; + if (e >= 0) { + m = 0x007fffff >> e; + if ((u.i & m) == 0) + return x; + FORCE_EVAL(x + 0x1p120f); + if (u.i >> 31 == 0) + u.i += m; + u.i &= ~m; + } else { + FORCE_EVAL(x + 0x1p120f); + if (u.i >> 31) + u.f = -0.0; + else if (u.i << 1) + u.f = 1.0; + } + return u.f; +} diff --git a/main.lds b/main.lds index 271d758..3a8d78a 100644 --- a/main.lds +++ b/main.lds @@ -6,7 +6,15 @@ MEMORY } SECTIONS { - . = ORIGIN(p1ram); + . = ORIGIN(p2ram); + + .text ALIGN(4) : SUBALIGN(4) + { + KEEP(*(.text.start)) + *(.text.startup.*) + } > p2ram AT>p1ram + + . = ORIGIN(p1ram) + (. - ORIGIN(p2ram)); .text ALIGN(4) : SUBALIGN(4) { @@ -34,16 +42,6 @@ SECTIONS KEEP(*(.ctors.*)) } > p1ram - . = ORIGIN(p2ram) + (. - ORIGIN(p1ram)); - - .text.p2ram ALIGN(4) : SUBALIGN(4) - { - *(.p2ram) - *(.p2ram.*) - } > p2ram AT>p1ram - - . = ORIGIN(p1ram) + (. - ORIGIN(p2ram)); - .bss ALIGN(4) (NOLOAD) : SUBALIGN(4) { *(.bss) diff --git a/regs/gen/core_bits.py b/regs/gen/core_bits.py index f48e345..2b66ddb 100644 --- a/regs/gen/core_bits.py +++ b/regs/gen/core_bits.py @@ -10,7 +10,7 @@ from generate import renderer def aggregate_registers(d): aggregated = defaultdict(list) for row in d: - assert row["register_name"] != "" + #assert row["register_name"] != "" aggregated[row["register_name"]].append(row) return dict(aggregated) @@ -218,7 +218,8 @@ def render_enum(enum_def): yield "}" def render_register(register): - yield f"namespace {register.name.lower()} {{" + if register.name != "": + yield f"namespace {register.name.lower()} {{" last = None for ix, bit_def in enumerate(register.defs): @@ -232,10 +233,14 @@ def render_register(register): yield from render_defs(bit_def) last = bit_def - yield "}" + if register.name != "": + yield "}" yield "" -def render_registers(registers): +def render_registers(registers, file_namespace): + if file_namespace is not None: + yield f"namespace {file_namespace} {{" + last_block = None for register in registers: if register.block != last_block: @@ -253,6 +258,9 @@ def render_registers(registers): if last_block is not None: yield '}' # end of block namespace + if file_namespace is not None: + yield '}' # end of file namespace + def header(): yield "#pragma once" yield "" @@ -263,9 +271,10 @@ def header(): if __name__ == "__main__": d = read_input(sys.argv[1]) + file_namespace = sys.argv[2] if len(sys.argv) > 2 else None aggregated = aggregate_registers(d) registers = aggregate_all_enums(aggregated) render, out = renderer() render(header()) - render(render_registers(registers)) + render(render_registers(registers, file_namespace)) sys.stdout.write(out.getvalue()) diff --git a/regs/object_list.csv b/regs/object_list.csv new file mode 100644 index 0000000..27106b6 --- /dev/null +++ b/regs/object_list.csv @@ -0,0 +1,28 @@ +"register_name","enum_name","bits","bit_name","value","mask","description" +,"pointer_type","31-29","triangle_strip","0b000",, +,"pointer_type","31-29","triangle_array","0b100",, +,"pointer_type","31-29","quad_array","0b101",, +,"pointer_type","31-29","object_pointer_block_link","0b111",, +,,,,,, +"triangle_strip","mask","30-25","t0","0b100000",, +"triangle_strip","mask","30-25","t1","0b010000",, +"triangle_strip","mask","30-25","t2","0b001000",, +"triangle_strip","mask","30-25","t3","0b000100",, +"triangle_strip","mask","30-25","t4","0b000010",, +"triangle_strip","mask","30-25","t5","0b000001",, +"triangle_strip",,"24","shadow","1",, +"triangle_strip",,"23-21","skip",,"0b111", +"triangle_strip",,"20-0","start",,"0x1fffff", +,,,,,, +"triangle_array",,"28-25","number_of_triangles",,"0b1111", +"triangle_array",,"24","shadow","1",, +"triangle_array",,"23-21","skip",,"0b111", +"triangle_array",,"20-0","start",,"0x1fffff", +,,,,,, +"quad_array",,"28-25","number_of_quads",,"0b1111", +"quad_array",,"24","shadow","1",, +"quad_array",,"23-21","skip",,"0b111", +"quad_array",,"20-0","start",,"0x1fffff", +,,,,,, +"object_pointer_block_link",,"28","end_of_list","1",, +"object_pointer_block_link",,"23-0","next_pointer_block",,"0xfffffc", diff --git a/regs/object_list.ods b/regs/object_list.ods new file mode 100644 index 0000000000000000000000000000000000000000..b3408e6909c51995077f63763e2652c7d1f0dfe4 GIT binary patch literal 15687 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 literal 0 HcmV?d00001