Add documentation.

This commit is contained in:
giomba 2022-12-03 22:20:34 +01:00
parent c23bcc7f43
commit ac3116a339
2 changed files with 136 additions and 25 deletions

View File

@ -22,12 +22,26 @@ typedef struct PIORun
uint sm; //< executing SM uint sm; //< executing SM
} PIORun; } PIORun;
/*
There are 3 SM running: horizontal and vertical sync, and pixels.
See vga.pio for details.
*/
static PIORun vga_hsync, vga_vsync, vga_pixel; static PIORun vga_hsync, vga_vsync, vga_pixel;
static bool dma_ready = false;
static int dma_channel;
static unsigned long int frame_counter = 0; static bool dma_ready = false; //< current DMA acceleration status
static int dma_channel; //< DMA channel number, used to refill the vga_pixel FIFO
static unsigned long int frame_counter = 0; //< current frame
/**
* @brief Setup system clocks.
*
* Main system clock must run at 126MHz,
* which is 4 times pixel clock (31.5 MHz).
* This allows SM to output one pixel every 4 clock cycles,
* and to do some stuff in the mean time (eg. sending interrupts to sync with the other SM)
*
*/
static void setup_clocks(void) static void setup_clocks(void)
{ {
// disable resuscitation clock // disable resuscitation clock
@ -52,9 +66,16 @@ static void setup_clocks(void)
clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 126 * MHZ, 126 * MHZ); clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 126 * MHZ, 126 * MHZ);
} }
/**
* @brief Initializes the VGA pixel shift machine.
*
* @param pio PIO peripheral to use.
* @param sm SM to use.
* @param offset PIO memory offset for program.
*/
static void vga_pixel_program_init(PIO pio, uint sm, uint offset) static void vga_pixel_program_init(PIO pio, uint sm, uint offset)
{ {
// config function automatically declared by SDK scripts // config function is automatically declared by SDK scripts
// based on the names given to programs in .pio // based on the names given to programs in .pio
pio_sm_config config = vga_pixel_program_get_default_config(offset); pio_sm_config config = vga_pixel_program_get_default_config(offset);
@ -72,6 +93,13 @@ static void vga_pixel_program_init(PIO pio, uint sm, uint offset)
pio_sm_set_enabled(pio, sm, true); pio_sm_set_enabled(pio, sm, true);
} }
/**
* @brief Initializes the VGA horizontal sync machine.
*
* @param pio PIO peripheral to use.
* @param sm SM to use.
* @param offset PIO memory offset for program.
*/
static void vga_hsync_program_init(PIO pio, uint sm, uint offset) static void vga_hsync_program_init(PIO pio, uint sm, uint offset)
{ {
pio_sm_config config = vga_hsync_program_get_default_config(offset); pio_sm_config config = vga_hsync_program_get_default_config(offset);
@ -90,6 +118,13 @@ static void vga_hsync_program_init(PIO pio, uint sm, uint offset)
pio_sm_set_enabled(pio, sm, true); pio_sm_set_enabled(pio, sm, true);
} }
/**
* @brief Initializes the VGA vertical sync machine.
*
* @param pio PIO peripheral to use.
* @param sm SM to use.
* @param offset PIO memory offset for program.
*/
static void vga_vsync_program_init(PIO pio, uint sm, uint offset) static void vga_vsync_program_init(PIO pio, uint sm, uint offset)
{ {
pio_sm_config config = vga_vsync_program_get_default_config(offset); pio_sm_config config = vga_vsync_program_get_default_config(offset);
@ -108,13 +143,20 @@ static void vga_vsync_program_init(PIO pio, uint sm, uint offset)
pio_sm_set_enabled(pio, sm, true); pio_sm_set_enabled(pio, sm, true);
} }
void dma_handler(void) /**
* @brief DMA interrupt handler.
*/
static void dma_handler(void)
{ {
// acknoweledge DMA // acknoweledge DMA
dma_hw->ints0 = 1 << dma_channel; dma_hw->ints0 = 1 << dma_channel;
} }
void new_frame_handler(void) /**
* @brief Vertical sync interrupt handler.
*
*/
static void new_frame_handler(void)
{ {
if (dma_ready) if (dma_ready)
{ {
@ -142,7 +184,6 @@ void new_frame_handler(void)
void vga_machines_init(void) void vga_machines_init(void)
{ {
// setup a proper system clock // setup a proper system clock
// (126MHz = 4 x 31.5 MHz pixel clock)
setup_clocks(); setup_clocks();
// Running programs on PIO // Running programs on PIO
@ -162,7 +203,10 @@ void vga_machines_init(void)
vga_hsync.offset = pio_add_program(vga_hsync.pio, &vga_hsync_program); vga_hsync.offset = pio_add_program(vga_hsync.pio, &vga_hsync_program);
vga_hsync.sm = pio_claim_unused_sm(vga_hsync.pio, true); vga_hsync.sm = pio_claim_unused_sm(vga_hsync.pio, true);
vga_hsync_program_init(vga_hsync.pio, vga_hsync.sm, vga_hsync.offset); vga_hsync_program_init(vga_hsync.pio, vga_hsync.sm, vga_hsync.offset);
// low pulse is X pixels long, and SM runs at 4x of pixel clock
// push configuration words to horizontal sync machine FIFO
// - hsync pulse duration (in pixel), assert time (low)
// - hsync pulse duration (in pixel), idle time (high), minus back porch
pio_sm_put_blocking(vga_hsync.pio, vga_hsync.sm, (64 - 2)); pio_sm_put_blocking(vga_hsync.pio, vga_hsync.sm, (64 - 2));
pio_sm_put_blocking(vga_hsync.pio, vga_hsync.sm, (640 + 16 - 1)); // 16 is front porch pio_sm_put_blocking(vga_hsync.pio, vga_hsync.sm, (640 + 16 - 1)); // 16 is front porch
printf("OK\n"); printf("OK\n");
@ -174,7 +218,9 @@ void vga_machines_init(void)
vga_vsync.offset = pio_add_program(vga_vsync.pio, &vga_vsync_program); vga_vsync.offset = pio_add_program(vga_vsync.pio, &vga_vsync_program);
vga_vsync.sm = pio_claim_unused_sm(vga_vsync.pio, true); vga_vsync.sm = pio_claim_unused_sm(vga_vsync.pio, true);
vga_vsync_program_init(vga_vsync.pio, vga_vsync.sm, vga_vsync.offset); vga_vsync_program_init(vga_vsync.pio, vga_vsync.sm, vga_vsync.offset);
// low pulse assert lasts for 3 horizontal lines, then adjust by one // push configuration words to vertical sync machine FIFO
// - vsync pulse duration (in lines), assert time (low)
// - vsync pulse duration (in lines), idle time (high)
pio_sm_put_blocking(vga_vsync.pio, vga_vsync.sm, 3 - 1); pio_sm_put_blocking(vga_vsync.pio, vga_vsync.sm, 3 - 1);
pio_sm_put_blocking(vga_vsync.pio, vga_vsync.sm, 500 - 3 - 1); pio_sm_put_blocking(vga_vsync.pio, vga_vsync.sm, 500 - 3 - 1);
printf("OK\n"); printf("OK\n");
@ -186,6 +232,8 @@ void vga_machines_init(void)
vga_pixel.offset = pio_add_program(vga_pixel.pio, &vga_pixel_program); vga_pixel.offset = pio_add_program(vga_pixel.pio, &vga_pixel_program);
vga_pixel.sm = pio_claim_unused_sm(vga_pixel.pio, false); vga_pixel.sm = pio_claim_unused_sm(vga_pixel.pio, false);
vga_pixel_program_init(vga_pixel.pio, vga_pixel.sm, vga_pixel.offset); vga_pixel_program_init(vga_pixel.pio, vga_pixel.sm, vga_pixel.offset);
// push configuration word to pixel machine FIFO
// - number of visible pixels in a line
pio_sm_put_blocking(vga_pixel.pio, vga_pixel.sm, 640 - 1); pio_sm_put_blocking(vga_pixel.pio, vga_pixel.sm, 640 - 1);
printf("OK\n"); printf("OK\n");
@ -195,16 +243,18 @@ void vga_machines_init(void)
channel_config_set_transfer_data_size(&dma_config, DMA_SIZE_32); channel_config_set_transfer_data_size(&dma_config, DMA_SIZE_32);
channel_config_set_read_increment(&dma_config, true); channel_config_set_read_increment(&dma_config, true);
channel_config_set_write_increment(&dma_config, false); channel_config_set_write_increment(&dma_config, false);
// link Data Request signal of PIO SM2 TX FIFO to DMA
channel_config_set_dreq(&dma_config, DREQ_PIO0_TX2); channel_config_set_dreq(&dma_config, DREQ_PIO0_TX2);
// setup transfer acknowledge interrupt
dma_channel_set_irq0_enabled(dma_channel, true); dma_channel_set_irq0_enabled(dma_channel, true);
irq_set_exclusive_handler(DMA_IRQ_0, dma_handler); irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
irq_set_enabled(DMA_IRQ_0, true); irq_set_enabled(DMA_IRQ_0, true);
// -1 because first word is manually injected // pull one word less, because first word is manually injected
// before the first DMA request, in order to // before the first DMA request, in order to avoid any possible delay
// avoid any possible delay // (see new frame interrupt handler)
dma_channel_configure(dma_channel, &dma_config, &pio0_hw->txf[2], &frame[0], dma_channel_configure(dma_channel, &dma_config, &pio0_hw->txf[2], &frame[0],
HPIXEL * (VSPULSE + VPIXEL + VBBLANK) / 32 - 1, true); HPIXEL * (VSPULSE + VPIXEL + VBBLANK) / 32 - 1, true);
@ -214,6 +264,11 @@ void vga_machines_init(void)
printf("Now running.\n"); printf("Now running.\n");
} }
/**
* @brief Get current frame counter.
*
* @return Current frame counter.
*/
unsigned long int vga_get_frame_counter(void) unsigned long int vga_get_frame_counter(void)
{ {
return frame_counter; return frame_counter;

View File

@ -1,60 +1,116 @@
/*
- horizontal sync pulse generation
this machine commands the timing for the other machines too
- vertical sync pulse generation
counts the horizontal sync pulses and generated a vertical sync pulse accordingly
- pixel machine
horizontal sync machine also sends an interrupt when the back porch is finished,
thus triggering the pixel machine to start shifting out the pixels
*/
/*
Pixel Machine
Wait for the "start-of-visible-line" interrupt signal,
and output 640 pixels
*/
.program vga_pixel .program vga_pixel
entrypoint_vga_pixel: entrypoint_vga_pixel:
// pull configuration into Y register
// Y = number of visible pixels in the line
pull pull
mov y, osr mov y, osr
.wrap_target .wrap_target
mov x, y mov x, y
wait irq 7 wait irq 7
// loop lasts 4 SM clocks, which corresponds to 1 VGA pixel
loop: loop:
out pins, 1 [2] out pins, 1 [2]
jmp x-- loop jmp x-- loop
.wrap .wrap
/*
Horizontal Sync Machine
A line is composed by 840 pixels:
- 64 sync pulse
- 120 back porch
- 640 visible area
- 16 front porch
*/
.program vga_hsync .program vga_hsync
entrypoint_vga_hsync: entrypoint_vga_hsync:
// pull configuration into ISR and OSR registers
// ISR = sync pulse duration - 2 (64 pixel)
// OSR = visible line + front porch - 1 (640 + 16 pixel)
// Note: configuration contains desired value -1/-2,
// because of how end-of-loop check is performed
// and how time is spent in overhead instructions
pull pull
mov isr, osr mov isr, osr
pull pull
// once configured, no more data is pulled from the TX FIFO,
// so program wraps around here
.wrap_target .wrap_target
set pins, 0 [1] set pins, 0 [1] // start pulse low
irq set 0 irq set 0 // trigger "start-of-line" interrupt signal
mov x, isr mov x, isr // load register X with sync pulse duration
// +4 SM clocks elapsed (first VGA pixel)
// loop for the duration of the low pulse (remaining 63 pixels)
hsync_pulse: hsync_pulse:
jmp x-- hsync_pulse [3] jmp x-- hsync_pulse [3]
set pins, 1 [1] set pins, 1 [1] // start pulse high (.5 pixel)
set y, 27 // hardcoded backporch duration: 120 pixel => 120 x 4 = 480 SM clocks
// but one pixel is already in the surronding logic, then only 476 clocks are cycled
// where 476 = (27 + 1) * (16 + 1)
set y, 27 // another .25 pixel
hsync_back_porch: hsync_back_porch:
jmp y-- hsync_back_porch [16] jmp y-- hsync_back_porch [16]
irq set 7 irq set 7 // trigger "start-of-visible-line" interrupt signal
// (.25 more pixel)
mov x, osr [3] mov x, osr [3] // load register X with remaining line duration (visible line + front porch)
// also, just make this last 4 SM clocks (1 pixel)
hsync_idle: hsync_idle:
jmp x-- hsync_idle [3] jmp x-- hsync_idle [3]
.wrap .wrap
/*
Vertical Sync Machine
A frame is composed by 500 lines:
- 3 vertical sync pulse
- 16 back porch
- 480 visible lines
- 1 front porch
*/
.program vga_vsync .program vga_vsync
entrypoint_vga_vsync: entrypoint_vga_vsync:
// Pull configuration into ISR and OSR registers
// ISR = sync pulse assert duration - 1 (3 lines)
// OSR = sync pulse idle duration - 1 (497 lines)
pull pull
mov isr, osr mov isr, osr
pull pull
// once configured, no more data is pulled from the TX FIFO,
// so program wraps around here
.wrap_target .wrap_target
mov x, isr mov x, isr // load register X with low pulse duration
vsync_pulse: vsync_pulse:
wait irq 0 wait irq 0 // wait for "start-of-line" interrupt from hsync machine
set pins, 0 set pins, 0 // start pulse low
jmp x-- vsync_pulse jmp x-- vsync_pulse
// trigger "start-of-frame" interrupt
// (actually, 3 lines of the back porch are already passed)
irq set 1 irq set 1
mov x, osr mov x, osr // load register X with high pulse duration
vsync_idle: vsync_idle:
wait irq 0 wait irq 0
set pins, 1 set pins, 1