From d645c1ada2bd34f29b7bd09945730eca354200d3 Mon Sep 17 00:00:00 2001 From: giomba Date: Sat, 1 Oct 2022 22:43:18 +0200 Subject: [PATCH] Add documentation. --- frame.cpp | 176 +++++++++++++++++++++++++++++++++++------------------- main.S | 2 +- 2 files changed, 117 insertions(+), 61 deletions(-) diff --git a/frame.cpp b/frame.cpp index f61a870..6b1a2b4 100644 --- a/frame.cpp +++ b/frame.cpp @@ -2,159 +2,215 @@ * @file frame.cpp * @author giomba@glgprograms.it * @brief Transform a black and white PNM image into the assembly code for the video generator. - * + * * Takes a black/white (1bpp) PNM ASCII-armored image as input, * and produces the frame.S assembly code to draw it * as an analog video signal for this project's kernel. - * + * * @copyright Copyright retrofficina.glgprograms.it (c) 2021-2022 - * + * */ #include #include #include #include +#include -enum ExitStatus { +enum ExitStatus +{ BAD_ARGUMENT = 1, FILE_NOT_OPEN = 2, FILE_BAD_FORMAT = 3 }; -int main(int argc, char** argv) { - if (argc < 2) { - std::cerr << "Usage: " << argv[0] << " " << std::endl; +int main(int argc, char **argv) +{ + // enable debug messages + spdlog::set_level(spdlog::level::debug); + + // arguments parsing and sanity check + if (argc < 2) + { + spdlog::error("Usage: {} ...", argv[0]); return BAD_ARGUMENT; } const int images = argc - 1; - std::stringstream jump_table; - std::stringstream asm_code; + // Output assembly file content. + // Content is buffered into these stringstreams, + // and then dumped to a file on disk. + // Code is organized into two parts. + std::stringstream jump_table; // Assembly jump table for images. + // Each image is described by a jump table with 256 entries. + // Each entry is a jump to the code to produce a particular line on the screen. + std::stringstream asm_code; // Assembly code to describe lines. + // Each line is a sequence of a set and/or clear bit, + // conveniently timed to mangle the video signal at the right moment. + + // Output file. std::fstream outfile; outfile.open("frame.S", std::ios::out); - if (! outfile) { + if (!outfile) return FILE_NOT_OPEN; - } + // include headers jump_table << "#include \"macro.h\"" << std::endl; jump_table << "#include " << std::endl; - char** image = new char*[images]; + // Pointer to images + char **image = new char *[images]; - for (int current_image = 0; current_image < images; ++current_image) { + // Parse images and convert in assembly code + for (int current_image = 0; current_image < images; ++current_image) + { std::fstream infile; infile.open(argv[1 + current_image], std::ios::in); - if (! infile) { + if (!infile) return FILE_NOT_OPEN; - } - int width, height; - std::string format; - char c; + unsigned int width, height; // width and height of image + std::string format; // image format + char c; // current pixel getline(infile, format); - - std::cout << format << std::endl; - - if (format != "P1") { + spdlog::debug("format: {}", format); + if (format != "P1") return FILE_BAD_FORMAT; - } - // skip one line TODO + // skip commented line / TODO in a proper way getline(infile, format); - std::cout << format << std::endl; + spdlog::debug("comment: {}", format); + // read width and height infile >> width >> height; - std::cout << width << " x " << height << std::endl; + spdlog::debug("width x height = {} x {}", width, height); + // read full image from disk into memory + // in an organized row by column array image[current_image] = new char[height * width]; - - for (int y = 0; y < height; ) { - for (int x = 0; x < width; ) { + for (int y = 0; y < height;) + { + for (int x = 0; x < width;) + { c = infile.get(); - if (! infile.good()) + + // file is truncated + if (!infile.good()) return FILE_BAD_FORMAT; - if (c == '0' || c == '1') { + + // if current character represents a pixel, add to array... + if (c == '0' || c == '1') + { image[current_image][y * width + x] = c; x++; - } else { - continue; } + else // ... else ignore current char + continue; } y++; } - infile.close(); + // add new jump table for current image jump_table << ".global line_jump_table_" << current_image << std::endl; jump_table << "line_jump_table_" << current_image << ":" << std::endl; - for (int y = 0; y < height; ++y) { - + // Inspect each line one by one + for (int y = 0; y < height; ++y) + { int diff = 1; - for (int other_image = 0; other_image <= current_image; ++other_image) { - for (int other_y = 0; other_y < height && other_y < y; ++other_y) { + // Check if one of the previously inspected images has an identical line. + // If it has an identical line, just add it in the current image jump table, + // instead of generating again an identical assembly code. + // This allows us to save a lot of program memory space. + // Note: previous lines of current image are good candidates to check too! + for (int other_image = 0; other_image <= current_image; ++other_image) + { + for (int other_y = 0; other_y < height && other_y < y; ++other_y) + { diff = memcmp(&(image[current_image][y * width + 0]), &(image[other_image][other_y * width + 0]), width); - if (diff == 0) { + if (diff == 0) + { jump_table << '\t' << "jmp line_" << other_image << "_" << other_y << std::endl; goto nested_loop_end; } } } -nested_loop_end: + nested_loop_end: + // Current line has already been found to be identical + // to one of the previous ones: then skip to next one. if (diff == 0) continue; + // Add jump in the current image table. jump_table << '\t' << "jmp line_" << current_image << "_" << y << std::endl; + + // Add line description. asm_code << "line_" << current_image << "_" << y << ":" << std::endl; + // Line description is a sequence of assembly code conveniently built + // in order to set/clear an output port bit at the right moment, + // and mangle the video signal in order to display the desired image. + // Code simulates an RLE scheme, exploting small conveniently timed loops, + // in order to save program space. int count = 0; char last = image[current_image][y * width + 0]; - for (int x = 0; x < width; ++x) { + for (int x = 0; x < width; ++x) + { char current = image[current_image][y * width + x]; - std::cout << "current " << current << " last " << last << std::endl; - if (current == last) { + // If current pixel is the same as previous one, + // then just count++ + if (current == last) ++count; - } else { - std::cout << "detected change" << std::endl; - if (last == '0') { - std::cout << "clear" << std::endl; - asm_code << '\t' << "cbi IO(PORTB), 4" << std::endl; - } else if (last == '1') { - std::cout << "set" << std::endl; - asm_code << '\t' << "sbi IO(PORTB), 4" << std::endl; + else // current pixel is different from previous one + { + // Each pixel is 4 clock cycles long, + // because It Just Works out perfectly with the instruction set. + + // Two clock cycles are spent to set/clear the output port. + if (last == '0') + { + asm_code << '\t' << "cbi IO(PORTB), 4" << std::endl; // +2 + } + else if (last == '1') + { + asm_code << '\t' << "sbi IO(PORTB), 4" << std::endl; // +2 } - if (count > 1) { - asm_code << '\t' << "ldi r31, " << count - 1 << std::endl; - asm_code << "1:" << std::endl; - asm_code << '\t' << "dec r31" << std::endl; - asm_code << '\t' << "nop" << std::endl; - asm_code << '\t' << "brne 1b" << std::endl; + // Loop is entered only if RLE is needed, + // eg. at least 2 subsequent pixels of the same color. + if (count > 1) + { // clock cycles + asm_code << '\t' << "ldi r31, " << count - 1 << std::endl; // +1 + asm_code << "1:" << std::endl; // +0 + asm_code << '\t' << "dec r31" << std::endl; // +1 + asm_code << '\t' << "nop" << std::endl; // +1 + asm_code << '\t' << "brne 1b" << std::endl; // +2 if taken, +1 if not } - asm_code << '\t' << "rjmp 2f" << std::endl; + // Add 2 more dummy clock cycles. + asm_code << '\t' << "rjmp 2f" << std::endl; // +2 asm_code << "2:" << std::endl; last = current; count = 1; } } + // At the end of the line, clear output bit, and return from interrupt. asm_code << '\t' << "cbi IO(PORTB), 4" << std::endl; asm_code << '\t' << "jmp jump_table_return_address" << std::endl; } - } delete[] image; + // save the generated code on disk outfile << jump_table.str() << asm_code.str(); - outfile.close(); } diff --git a/main.S b/main.S index ddc14f7..a015f2d 100644 --- a/main.S +++ b/main.S @@ -81,7 +81,7 @@ int_horizontal_sync: ; +3 jmp int_horizontal_sync_end enter: - ; here, +23 or +24 cycles have passed since horizontal sync + ; here, +23 cycles have passed since horizontal sync ; so, there are still ~168 cycles before first useful data ldi r31, 0 sts TCNT0, r31