Skip to content

Commit

Permalink
Add second sync bit to flag errors back to the Sega
Browse files Browse the repository at this point in the history
Also split firmware speed tests out into their own file
  • Loading branch information
joeyparrish committed Jun 16, 2024
1 parent 8b3e098 commit 85a0f33
Show file tree
Hide file tree
Showing 14 changed files with 13,587 additions and 8,179 deletions.
12 changes: 8 additions & 4 deletions hardware/firmware/fast-gpio.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
#define SRAM_PIN__DATA_CLOCK 22
#define SRAM_PIN__DATA_WRITE 14

#define SYNC_PIN__READY 10
#define SYNC_PIN__CLEAR 11
#define SYNC_PIN__CMD_READY 10
#define SYNC_PIN__CMD_CLEAR 11
#define SYNC_PIN__ERR_FLAGGED 27
#define SYNC_PIN__ERR_SET 26

#define REG_PIN__A0 8
#define REG_PIN__A1 9
Expand Down Expand Up @@ -64,8 +66,10 @@
#define SRAM_PIN__DATA_CLOCK 0
#define SRAM_PIN__DATA_WRITE 0

#define SYNC_PIN__READY 0
#define SYNC_PIN__CLEAR 0
#define SYNC_PIN__CMD_READY 0
#define SYNC_PIN__CMD_CLEAR 0
#define SYNC_PIN__ERR_FLAGGED 0
#define SYNC_PIN__ERR_SET 0

#define REG_PIN__A0 0
#define REG_PIN__A1 0
Expand Down
116 changes: 5 additions & 111 deletions hardware/firmware/firmware.ino
Original file line number Diff line number Diff line change
Expand Up @@ -15,91 +15,25 @@
#include "http.h"
#include "internet.h"
#include "registers.h"
#include "speed-tests.h"
#include "sram.h"

#define SERVER "storage.googleapis.com"
#define PATH "/sega-kinetoscope/canned-videos/NEVER_GONNA_GIVE_YOU_UP.segavideo"

// An actual MAC address assigned to me with my ethernet board.
// Don't put two of these devices on the same network, y'all.
uint8_t MAC_ADDR[] = { 0x98, 0x76, 0xB6, 0x12, 0xD4, 0x9E };

// ~3s worth of audio+video data with headers and worst-case padding.
#define ABOUT_3S_VIDEO_AUDIO_BYTES 901932

// A safe buffer size for these tests.
#define BUFFER_SIZE 100 * 1024

static long test_sram_speed(uint8_t* buffer, int bytes) {
// 100kB: ~83ms
// 1MB: ~830ms
// 3s video+audio: ~731ms
long start = millis();
sram_start_bank(0);
sram_write(buffer, bytes);
sram_flush();
long end = millis();
return end - start;
}

static long test_sync_token_read_speed() {
// ~114 ns per read
long start = millis();
int count = 0;
for (int i = 0; i < 1000000; ++i) {
count += is_sync_token_set();
}
long end = millis();
return end - start;
}

static long test_sync_token_clear_speed() {
// ~114 ns per clear
long start = millis();
for (int i = 0; i < 1000000; ++i) {
clear_sync_token();
}
long end = millis();
return end - start;
}

static long test_register_read_speed() {
// ~228 ns per read
int count = 0;
long start = millis();
for (int i = 0; i < 1000000; ++i) {
count += read_register(i & 3);
}
long end = millis();
return end - start;
}

static long test_download_speed(int first_byte, int total_size) {
// 2.5Mbps minimum required
// ~2.7Mbps with initial HTTP connection overhead
// ~3.0Mbps on subsequent requests @902kB
long start = millis();
sram_start_bank(0);
int bytes_read = http_fetch(SERVER, /* default port */ 0, PATH,
first_byte, total_size,
sram_write);
sram_flush();
long end = millis();
return end - start;
}

uint8_t* buffer;

void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); } // Wait for serial port to connect

// Delay startup so we can have the serial monitor attached.
delay(1000);
Serial.println("Hello, world!\n");
Serial.println("Kinetoscope boot!\n");

sram_init();
registers_init();
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);

// Prefer wired, fall back to WiFi if configured.
Client* client = internet_init_wired(MAC_ADDR);
Expand Down Expand Up @@ -128,49 +62,9 @@ void setup() {
}

http_init(client);

pinMode(LED_BUILTIN, OUTPUT);

buffer = (uint8_t*)malloc(BUFFER_SIZE);
if (!buffer) {
Serial.println("Failed to allocate test buffer!");
while (true) { delay(1000); }
}

Serial.println("\n");
}

void loop() {
long ms;

ms = test_sync_token_read_speed();
Serial.print(ms);
Serial.println(" ns avg per sync token read."); // 1Mx reads, ms => ns

ms = test_sync_token_clear_speed();
Serial.print(ms);
Serial.println(" ns avg per sync token clear."); // 1Mx reads, ms => ns

ms = test_register_read_speed();
Serial.print(ms);
Serial.println(" ns avg per register read."); // 1Mx reads, ms => ns

ms = test_sram_speed(buffer, BUFFER_SIZE);
Serial.print(ms);
Serial.print(" ms to write ");
Serial.print(BUFFER_SIZE);
Serial.println(" bytes to SRAM");

for (int i = 0; i < 10; i++) {
ms = test_download_speed(/* first_byte= */ i, ABOUT_3S_VIDEO_AUDIO_BYTES);
float bits = ABOUT_3S_VIDEO_AUDIO_BYTES * 8.0;
float seconds = ms / 1000.0;
float mbps = bits / seconds / 1024.0 / 1024.0;
Serial.print(ms);
Serial.print(" ms to stream ~3s video to SRAM (");
Serial.print(mbps);
Serial.println(" Mbps vs 2.50 Mbps minimum)");
}

run_tests();
while (true) { delay(1000); }
}
43 changes: 27 additions & 16 deletions hardware/firmware/registers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,48 @@

void registers_init() {
// Set modes on register and sync pins.
pinMode(REG_PIN__D0, INPUT);
pinMode(REG_PIN__D1, INPUT);
pinMode(REG_PIN__D2, INPUT);
pinMode(REG_PIN__D3, INPUT);
pinMode(REG_PIN__D4, INPUT);
pinMode(REG_PIN__D5, INPUT);
pinMode(REG_PIN__D6, INPUT);
pinMode(REG_PIN__D7, INPUT);
pinMode(REG_PIN__D0, INPUT_PULLDOWN);
pinMode(REG_PIN__D1, INPUT_PULLDOWN);
pinMode(REG_PIN__D2, INPUT_PULLDOWN);
pinMode(REG_PIN__D3, INPUT_PULLDOWN);
pinMode(REG_PIN__D4, INPUT_PULLDOWN);
pinMode(REG_PIN__D5, INPUT_PULLDOWN);
pinMode(REG_PIN__D6, INPUT_PULLDOWN);
pinMode(REG_PIN__D7, INPUT_PULLDOWN);

pinMode(REG_PIN__A0, OUTPUT);
pinMode(REG_PIN__A1, OUTPUT);

pinMode(SYNC_PIN__READY, INPUT);
pinMode(SYNC_PIN__CLEAR, OUTPUT);
pinMode(SYNC_PIN__CMD_READY, INPUT_PULLDOWN);
pinMode(SYNC_PIN__CMD_CLEAR, OUTPUT);
pinMode(SYNC_PIN__ERR_SET, OUTPUT);
pinMode(SYNC_PIN__ERR_FLAGGED, INPUT_PULLDOWN);

// Disable active-low signals by default (setting them high).
FAST_SET(SYNC_PIN__CLEAR);
FAST_SET(SYNC_PIN__CMD_CLEAR);
FAST_SET(SYNC_PIN__ERR_SET);

// Set other outputs low by default.
FAST_CLEAR(REG_PIN__A0);
FAST_CLEAR(REG_PIN__A1);

clear_sync_token();
clear_cmd();
}

int is_sync_token_set() {
return FAST_GET(SYNC_PIN__READY);
int is_cmd_set() {
return FAST_GET(SYNC_PIN__CMD_READY);
}

void clear_sync_token() {
FAST_PULSE_ACTIVE_LOW(SYNC_PIN__CLEAR);
void clear_cmd() {
FAST_PULSE_ACTIVE_LOW(SYNC_PIN__CMD_CLEAR);
}

void flag_error() {
FAST_PULSE_ACTIVE_LOW(SYNC_PIN__ERR_SET);
}

int is_err_flagged() {
return FAST_GET(SYNC_PIN__ERR_FLAGGED);
}

uint8_t read_register(int register_address) {
Expand Down
22 changes: 20 additions & 2 deletions hardware/firmware/registers.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,29 @@

#include <inttypes.h>

// These are the sega address bits A2 and A1. There is no A0 signal from the
// sega, since it has a 16-bit data bus.
#define KINETOSCOPE_REG_CMD 0 // Sega's 0xA13000
#define KINETOSCOPE_REG_ARG 1 // Sega's 0xA13002
// These exist in hardware but aren't used at the moment.
#define KINETOSCOPE_REG_UNUSED_2 2
#define KINETOSCOPE_REG_UNUSED_3 3

#define KINETOSCOPE_CMD_ECHO 0x00 // Writes arg to SRAM
#define KINETOSCOPE_CMD_LIST_VIDEOS 0x01 // Writes video list to SRAM
#define KINETOSCOPE_CMD_START_VIDEO 0x02 // Begins streaming to SRAM
#define KINETOSCOPE_CMD_STOP_VIDEO 0x03 // Stops streaming
#define KINETOSCOPE_CMD_FLIP_REGION 0x04 // Switch SRAM banks for streaming

void registers_init();

int is_sync_token_set();
int is_cmd_set();

void clear_cmd();

void flag_error();

void clear_sync_token();
int is_err_flagged();

uint8_t read_register(int register_address);

Expand Down
124 changes: 124 additions & 0 deletions hardware/firmware/speed-tests.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Kinetoscope: A Sega Genesis Video Player
//
// Copyright (c) 2024 Joey Parrish
//
// See MIT License in LICENSE.txt

// Microcontroller function speed tests.

#include <Arduino.h>
#include <HardwareSerial.h>

#include "http.h"
#include "registers.h"
#include "sram.h"

#define SERVER "storage.googleapis.com"
#define PATH "/sega-kinetoscope/canned-videos/NEVER_GONNA_GIVE_YOU_UP.segavideo"

// ~3s worth of audio+video data with headers and worst-case padding.
#define ABOUT_3S_VIDEO_AUDIO_BYTES 901932

// A safe buffer size for these tests.
#define BUFFER_SIZE 100 * 1024

static long test_sram_speed(uint8_t* buffer, int bytes) {
// 100kB: ~83ms
// 1MB: ~830ms
// 3s video+audio: ~731ms
long start = millis();
sram_start_bank(0);
sram_write(buffer, bytes);
sram_flush();
long end = millis();
return end - start;
}

static long test_sync_token_read_speed() {
// ~114 ns per read
long start = millis();
int count = 0;
for (int i = 0; i < 1000000; ++i) {
count += is_cmd_set();
}
long end = millis();
return end - start;
}

static long test_sync_token_clear_speed() {
// ~114 ns per clear
long start = millis();
for (int i = 0; i < 1000000; ++i) {
clear_cmd();
}
long end = millis();
return end - start;
}

static long test_register_read_speed() {
// ~228 ns per read
int count = 0;
long start = millis();
for (int i = 0; i < 1000000; ++i) {
count += read_register(i & 3);
}
long end = millis();
return end - start;
}

static long test_download_speed(int first_byte, int total_size) {
// 2.5Mbps minimum required
// ~2.7Mbps with initial HTTP connection overhead
// ~3.0Mbps on subsequent requests @902kB
long start = millis();
sram_start_bank(0);
int bytes_read = http_fetch(SERVER, /* default port */ 0, PATH,
first_byte, total_size,
sram_write);
sram_flush();
long end = millis();
return end - start;
}

void run_tests() {
long ms;

uint8_t* buffer = NULL;
buffer = (uint8_t*)malloc(BUFFER_SIZE);
if (!buffer) {
Serial.println("Failed to allocate buffer!");
while (true) { delay(1000); }
}

ms = test_sync_token_read_speed();
Serial.print(ms);
Serial.println(" ns avg per sync token read."); // 1Mx reads, ms => ns

ms = test_sync_token_clear_speed();
Serial.print(ms);
Serial.println(" ns avg per sync token clear."); // 1Mx reads, ms => ns

ms = test_register_read_speed();
Serial.print(ms);
Serial.println(" ns avg per register read."); // 1Mx reads, ms => ns

ms = test_sram_speed(buffer, BUFFER_SIZE);
Serial.print(ms);
Serial.print(" ms to write ");
Serial.print(buffer_size);
Serial.println(" bytes to SRAM");

for (int i = 0; i < 10; i++) {
ms = test_download_speed(/* first_byte= */ i, ABOUT_3S_VIDEO_AUDIO_BYTES);
float bits = ABOUT_3S_VIDEO_AUDIO_BYTES * 8.0;
float seconds = ms / 1000.0;
float mbps = bits / seconds / 1024.0 / 1024.0;
Serial.print(ms);
Serial.print(" ms to stream ~3s video to SRAM (");
Serial.print(mbps);
Serial.println(" Mbps vs 2.50 Mbps minimum)");
}

Serial.println("\n");
free(buffer);
}
Loading

0 comments on commit 85a0f33

Please sign in to comment.