feat: add tests for JPEG reference model and register synchronization

- Implemented tests for the deterministic baseline JPEG reference model in , covering encoding, decoding, quality scaling, and error handling.
- Created  to ensure synchronization between RTL and C register constants for JPEG.
- Updated  to reflect changes in the number of APB interfaces and IRQ mappings, ensuring the topology generates correct bindings.
This commit is contained in:
maksyuki committed 2026-08-31 19:23:19 +08:00
1 parent d164a562ad
commit 39bc1c84b4
75 files changed
+15048 -58

No files matched your search

+9 -2
View File
@@ -52,8 +52,9 @@ MPW compatibility API; selector mutators return `RS_ENOTSUP` in product builds.
See the [extension contract](../docs/ip/extensions.md) and
[legacy user-IP contract](../docs/ip/user-ip.md).
`<retrosoc/hal/resource.h>` owns the central DMA, USB2, SDIO0/1, SPI-SD, and
EXT-H owner/lock, lifecycle-request, fault, and HP cache-maintenance handshake.
`<retrosoc/hal/resource.h>` owns the central DMA, USB2, SDIO0/1, SPI-SD,
EXT-H, and JPEG owner/lock, lifecycle-request, fault, and HP cache-maintenance
handshake.
See the [Resource Controller contract](../docs/ip/resource-controller.md).
`<retrosoc/hal/fabric_monitor.h>` owns root-management access to native AXI64
@@ -81,6 +82,12 @@ boundary and assumes public exponent 65537 for result verification. See the
[crypto controller contract](../docs/ip/crypto.md) before handling production
keys or composing an RFC 8017 scheme.
`<retrosoc/hal/jpeg.h>` provides validated direct jobs, bounded waits/abort,
status and IRQ access, encoder table portal operations, and 128-byte SG-ring
descriptors for the private-AXI Baseline JPEG codec. Metadata fields are
reserved and rejected in V1. See the [JPEG codec contract](../docs/ip/jpeg.md)
for the raster, cache, descriptor, and performance requirements.
`<retrosoc/hal/sdio.h>` provides separate low-level native SD host,
SD Memory v2, and SDIO function APIs for `sdio0` and `sdio1`. The controller
scope is fixed-present 3.3 V SDR at 1/4-bit width; it does not claim SD
+115
View File
@@ -0,0 +1,115 @@
#ifndef RETROSOC_HAL_JPEG_H
#define RETROSOC_HAL_JPEG_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <retrosoc/core/status.h>
#include <retrosoc/hal/jpeg_regs.h>
typedef enum {
RS_JPEG_MODE_ENCODE = 0,
RS_JPEG_MODE_DECODE = 1,
} rs_jpeg_mode_t;
typedef enum {
RS_JPEG_FORMAT_GRAY8 = 0,
RS_JPEG_FORMAT_RGB565 = 1,
RS_JPEG_FORMAT_RGB888 = 2,
RS_JPEG_FORMAT_YUYV422 = 3,
RS_JPEG_FORMAT_NV12 = 4,
} rs_jpeg_pixel_format_t;
typedef enum {
RS_JPEG_SAMPLING_GRAY = 0,
RS_JPEG_SAMPLING_444 = 1,
RS_JPEG_SAMPLING_422 = 2,
RS_JPEG_SAMPLING_420 = 3,
} rs_jpeg_sampling_t;
typedef struct {
rs_jpeg_mode_t mode;
rs_jpeg_pixel_format_t input_format;
rs_jpeg_pixel_format_t output_format;
rs_jpeg_sampling_t sampling;
uint16_t width;
uint16_t height;
uint8_t quality;
uint8_t table_context;
uint16_t restart_interval;
uintptr_t bitstream;
uint32_t bitstream_size;
uintptr_t planes[3];
uint32_t strides[3];
uintptr_t metadata;
uint32_t metadata_length;
bool auto_header;
bool strict;
} rs_jpeg_job_t;
typedef struct {
uint32_t control;
uint32_t status;
uint32_t image_size;
uint32_t encode_config;
uint32_t restart_interval;
uint32_t bitstream_addr;
uint32_t bitstream_size;
uint32_t result_size;
uint32_t plane0_addr;
uint32_t plane0_stride;
uint32_t plane1_addr;
uint32_t plane1_stride;
uint32_t plane2_addr;
uint32_t plane2_stride;
uint32_t metadata_addr;
uint32_t metadata_length;
uint32_t cookie_lo;
uint32_t cookie_hi;
uint32_t result_image_size;
uint32_t result_format;
uint32_t cycles_lo;
uint32_t cycles_hi;
uint32_t input_bytes;
uint32_t output_bytes;
uint32_t reserved[8];
} rs_jpeg_descriptor_t;
typedef struct {
bool busy;
bool ring_active;
bool encode;
uint32_t irq_state;
uint32_t error_status;
uintptr_t error_address;
uint32_t result_size;
uint16_t result_width;
uint16_t result_height;
uint64_t cycles;
uint32_t input_bytes;
uint32_t output_bytes;
} rs_jpeg_status_t;
_Static_assert(sizeof(rs_jpeg_descriptor_t) == RS_JPEG_DESCRIPTOR_BYTES,
"JPEG descriptor ABI must be 128 bytes");
rs_status_t rs_jpeg_job_validate(const rs_jpeg_job_t *job);
rs_status_t rs_jpeg_descriptor_build(rs_jpeg_descriptor_t *descriptor, const rs_jpeg_job_t *job,
uint64_t cookie, bool interrupt);
rs_status_t rs_jpeg_ring_validate(const rs_jpeg_descriptor_t *ring, uint32_t entries);
rs_status_t rs_jpeg_configure(const rs_jpeg_job_t *job);
rs_status_t rs_jpeg_start(void);
rs_status_t rs_jpeg_wait(rs_timeout_t timeout);
rs_status_t rs_jpeg_abort(rs_timeout_t timeout);
rs_status_t rs_jpeg_get_status(rs_jpeg_status_t *status);
rs_status_t rs_jpeg_irq_enable(uint32_t mask);
rs_status_t rs_jpeg_irq_pending(uint32_t *mask);
rs_status_t rs_jpeg_irq_clear(uint32_t mask);
rs_status_t rs_jpeg_table_write(uint8_t context, uint8_t kind, uint8_t index, uint32_t value);
rs_status_t rs_jpeg_table_commit(uint8_t context);
rs_status_t rs_jpeg_table_clear(uint8_t context);
rs_status_t rs_jpeg_ring_init(rs_jpeg_descriptor_t *ring, uint32_t entries);
rs_status_t rs_jpeg_ring_submit(uint32_t tail);
#endif
+123
View File
@@ -0,0 +1,123 @@
#ifndef RETROSOC_HAL_JPEG_REGS_H
#define RETROSOC_HAL_JPEG_REGS_H
#include <stdint.h>
#include <retrosoc/core/soc.h>
#define RS_JPEG_REG_IP_ID UINT32_C(0x000)
#define RS_JPEG_REG_IP_VERSION UINT32_C(0x004)
#define RS_JPEG_REG_CAPABILITY0 UINT32_C(0x008)
#define RS_JPEG_REG_CAPABILITY1 UINT32_C(0x00C)
#define RS_JPEG_REG_COMMAND UINT32_C(0x010)
#define RS_JPEG_REG_STATUS UINT32_C(0x014)
#define RS_JPEG_REG_IRQ_STATE UINT32_C(0x018)
#define RS_JPEG_REG_IRQ_ENABLE UINT32_C(0x01C)
#define RS_JPEG_REG_IRQ_TEST UINT32_C(0x020)
#define RS_JPEG_REG_ERROR_STATUS UINT32_C(0x024)
#define RS_JPEG_REG_ERROR_ADDRESS UINT32_C(0x028)
#define RS_JPEG_REG_ERROR_DETAIL UINT32_C(0x02C)
#define RS_JPEG_REG_PERF_CONTROL UINT32_C(0x030)
#define RS_JPEG_REG_CYCLES_LO UINT32_C(0x034)
#define RS_JPEG_REG_CYCLES_HI UINT32_C(0x038)
#define RS_JPEG_REG_PIXELS UINT32_C(0x03C)
#define RS_JPEG_REG_INPUT_BYTES UINT32_C(0x040)
#define RS_JPEG_REG_OUTPUT_BYTES UINT32_C(0x044)
#define RS_JPEG_REG_READ_STALL UINT32_C(0x048)
#define RS_JPEG_REG_WRITE_STALL UINT32_C(0x04C)
#define RS_JPEG_REG_JOB_CONFIG UINT32_C(0x080)
#define RS_JPEG_REG_IMAGE_SIZE UINT32_C(0x084)
#define RS_JPEG_REG_INPUT_FORMAT UINT32_C(0x088)
#define RS_JPEG_REG_OUTPUT_FORMAT UINT32_C(0x08C)
#define RS_JPEG_REG_ENCODE_CONFIG UINT32_C(0x090)
#define RS_JPEG_REG_RESTART_INTERVAL UINT32_C(0x094)
#define RS_JPEG_REG_BITSTREAM_ADDR UINT32_C(0x098)
#define RS_JPEG_REG_BITSTREAM_SIZE UINT32_C(0x09C)
#define RS_JPEG_REG_PLANE0_ADDR UINT32_C(0x0A0)
#define RS_JPEG_REG_PLANE0_STRIDE UINT32_C(0x0A4)
#define RS_JPEG_REG_PLANE1_ADDR UINT32_C(0x0A8)
#define RS_JPEG_REG_PLANE1_STRIDE UINT32_C(0x0AC)
#define RS_JPEG_REG_PLANE2_ADDR UINT32_C(0x0B0)
#define RS_JPEG_REG_PLANE2_STRIDE UINT32_C(0x0B4)
#define RS_JPEG_REG_METADATA_ADDR UINT32_C(0x0B8)
#define RS_JPEG_REG_METADATA_LENGTH UINT32_C(0x0BC)
#define RS_JPEG_REG_RESULT_SIZE UINT32_C(0x0C0)
#define RS_JPEG_REG_RESULT_IMAGE_SIZE UINT32_C(0x0C4)
#define RS_JPEG_REG_RESULT_FORMAT UINT32_C(0x0C8)
#define RS_JPEG_REG_RESULT_MARKERS UINT32_C(0x0CC)
#define RS_JPEG_REG_RING_BASE UINT32_C(0x100)
#define RS_JPEG_REG_RING_SIZE UINT32_C(0x104)
#define RS_JPEG_REG_RING_HEAD UINT32_C(0x108)
#define RS_JPEG_REG_RING_TAIL UINT32_C(0x10C)
#define RS_JPEG_REG_RING_CONTROL UINT32_C(0x110)
#define RS_JPEG_REG_RING_STATUS UINT32_C(0x114)
#define RS_JPEG_REG_IRQ_COALESCE UINT32_C(0x118)
#define RS_JPEG_REG_DOORBELL UINT32_C(0x11C)
#define RS_JPEG_REG_TABLE_CONTEXT UINT32_C(0x200)
#define RS_JPEG_REG_TABLE_KIND UINT32_C(0x204)
#define RS_JPEG_REG_TABLE_INDEX UINT32_C(0x208)
#define RS_JPEG_REG_TABLE_DATA UINT32_C(0x20C)
#define RS_JPEG_REG_TABLE_COMMAND UINT32_C(0x210)
#define RS_JPEG_REG_TABLE_STATUS UINT32_C(0x214)
#define RS_JPEG_COMMAND_START UINT32_C(0x00000001)
#define RS_JPEG_COMMAND_ABORT UINT32_C(0x00000002)
#define RS_JPEG_COMMAND_SOFT_RESET UINT32_C(0x00000004)
#define RS_JPEG_COMMAND_RING_KICK UINT32_C(0x00000008)
#define RS_JPEG_STATUS_BUSY UINT32_C(0x00000001)
#define RS_JPEG_STATUS_RING_ACTIVE UINT32_C(0x00000002)
#define RS_JPEG_STATUS_ENCODE UINT32_C(0x00000004)
#define RS_JPEG_STATUS_IDLE UINT32_C(0x00000008)
#define RS_JPEG_IRQ_JOB_DONE UINT32_C(0x00000001)
#define RS_JPEG_IRQ_RING_EVENT UINT32_C(0x00000002)
#define RS_JPEG_IRQ_HEADER_READY UINT32_C(0x00000004)
#define RS_JPEG_IRQ_ABORT_DONE UINT32_C(0x00000008)
#define RS_JPEG_IRQ_ERROR UINT32_C(0x00000010)
#define RS_JPEG_IRQ_ALL UINT32_C(0x0000001F)
#define RS_JPEG_JOB_CONFIG_ENCODE UINT32_C(0x00000001)
#define RS_JPEG_JOB_CONFIG_AUTO_HEADER UINT32_C(0x00000002)
#define RS_JPEG_JOB_CONFIG_STRICT UINT32_C(0x00000004)
#define RS_JPEG_JOB_CONFIG_METADATA UINT32_C(0x00000008)
#define RS_JPEG_JOB_CONFIG_TABLE_SHIFT 8U
#define RS_JPEG_JOB_CONFIG_TABLE_MASK UINT32_C(0x00000300)
#define RS_JPEG_DESCRIPTOR_OWN UINT32_C(0x00000001)
#define RS_JPEG_DESCRIPTOR_IOC UINT32_C(0x00000002)
#define RS_JPEG_DESCRIPTOR_ENCODE UINT32_C(0x00000004)
#define RS_JPEG_DESCRIPTOR_AUTO_HEADER UINT32_C(0x00000008)
#define RS_JPEG_DESCRIPTOR_STRICT UINT32_C(0x00000010)
#define RS_JPEG_DESCRIPTOR_METADATA UINT32_C(0x00000020)
#define RS_JPEG_DESCRIPTOR_TABLE_SHIFT 8U
#define RS_JPEG_DESCRIPTOR_INPUT_SHIFT 12U
#define RS_JPEG_DESCRIPTOR_OUTPUT_SHIFT 16U
#define RS_JPEG_DESCRIPTOR_SAMPLE_SHIFT 20U
#define RS_JPEG_DESCRIPTOR_STATUS_DONE UINT32_C(0x00000001)
#define RS_JPEG_DESCRIPTOR_STATUS_ERROR UINT32_C(0x00000002)
#define RS_JPEG_DESCRIPTOR_STATUS_ABORT UINT32_C(0x00000004)
#define RS_JPEG_DESCRIPTOR_STATUS_CODE_SHIFT 8U
#define RS_JPEG_RING_CONTROL_ENABLE UINT32_C(0x00000001)
#define RS_JPEG_RING_CONTROL_STOP_ERROR UINT32_C(0x00000002)
#define RS_JPEG_RING_STATUS_ACTIVE UINT32_C(0x00000001)
#define RS_JPEG_RING_STATUS_EMPTY UINT32_C(0x00000002)
#define RS_JPEG_RING_STATUS_STALLED UINT32_C(0x00000004)
#define RS_JPEG_RING_STATUS_ERROR UINT32_C(0x00000008)
#define RS_JPEG_TABLE_COMMAND_COMMIT UINT32_C(0x00000001)
#define RS_JPEG_TABLE_COMMAND_DEFAULT UINT32_C(0x00000002)
#define RS_JPEG_TABLE_COMMAND_CLEAR UINT32_C(0x00000004)
#define RS_JPEG_TABLE_STATUS_VALID UINT32_C(0x00000008)
#define RS_JPEG_TABLE_STATUS_DIRTY UINT32_C(0x00000400)
#define RS_JPEG_TABLE_STATUS_ERROR UINT32_C(0x00000800)
#define RS_JPEG_MAX_DIMENSION 2048U
#define RS_JPEG_METADATA_MAX_BYTES UINT32_C(65535)
#define RS_JPEG_DESCRIPTOR_BYTES 128U
#define RS_JPEG_DESCRIPTOR_WORDS 32U
#define RS_JPEG_DESCRIPTOR_ALIGNMENT 128U
#define RS_JPEG_DMA_ALIGNMENT 8U
#define RS_JPEG_RING_MAX_ENTRIES 256U
#define RS_JPEG_REG(offset) RS_SOC_REG32(RS_SOC_APB4_JPEG_BASE, (offset))
#endif
+2 -1
View File
@@ -13,7 +13,8 @@ typedef enum {
RS_RESOURCE_SDIO1 = 3,
RS_RESOURCE_SPISD = 4,
RS_RESOURCE_EXT_H = 5,
RS_RESOURCE_COUNT = 6,
RS_RESOURCE_JPEG = 6,
RS_RESOURCE_COUNT = 7,
} rs_resource_t;
typedef enum {
+222
View File
@@ -0,0 +1,222 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <retrosoc/hal/jpeg.h>
static uint32_t rs_jpeg_job_config(const rs_jpeg_job_t *job) {
uint32_t config = (uint32_t)job->table_context << RS_JPEG_JOB_CONFIG_TABLE_SHIFT;
if (job->mode == RS_JPEG_MODE_ENCODE) {
config |= RS_JPEG_JOB_CONFIG_ENCODE;
}
if (job->auto_header) {
config |= RS_JPEG_JOB_CONFIG_AUTO_HEADER;
}
if (job->strict) {
config |= RS_JPEG_JOB_CONFIG_STRICT;
}
if (job->metadata_length != 0U) {
config |= RS_JPEG_JOB_CONFIG_METADATA;
}
return config;
}
static uint64_t rs_jpeg_read_cycles(void) {
uint32_t high_before;
uint32_t high_after;
uint32_t low;
do {
high_before = RS_JPEG_REG(RS_JPEG_REG_CYCLES_HI);
low = RS_JPEG_REG(RS_JPEG_REG_CYCLES_LO);
high_after = RS_JPEG_REG(RS_JPEG_REG_CYCLES_HI);
} while (high_before != high_after);
return ((uint64_t)high_after << 32U) | (uint64_t)low;
}
rs_status_t rs_jpeg_configure(const rs_jpeg_job_t *job) {
uint32_t encode_config;
if (rs_jpeg_job_validate(job) != RS_OK) {
return RS_EINVAL;
}
if ((RS_JPEG_REG(RS_JPEG_REG_STATUS) & RS_JPEG_STATUS_BUSY) != 0U) {
return RS_EIO;
}
encode_config = (uint32_t)job->quality | ((uint32_t)job->sampling << 8U);
RS_JPEG_REG(RS_JPEG_REG_IRQ_STATE) = RS_JPEG_IRQ_ALL;
RS_JPEG_REG(RS_JPEG_REG_ERROR_STATUS) = UINT32_C(1);
RS_JPEG_REG(RS_JPEG_REG_JOB_CONFIG) = rs_jpeg_job_config(job);
RS_JPEG_REG(RS_JPEG_REG_IMAGE_SIZE) = ((uint32_t)job->height << 16U) | (uint32_t)job->width;
RS_JPEG_REG(RS_JPEG_REG_INPUT_FORMAT) = (uint32_t)job->input_format;
RS_JPEG_REG(RS_JPEG_REG_OUTPUT_FORMAT) = (uint32_t)job->output_format;
RS_JPEG_REG(RS_JPEG_REG_ENCODE_CONFIG) = encode_config;
RS_JPEG_REG(RS_JPEG_REG_RESTART_INTERVAL) = (uint32_t)job->restart_interval;
RS_JPEG_REG(RS_JPEG_REG_BITSTREAM_ADDR) = (uint32_t)job->bitstream;
RS_JPEG_REG(RS_JPEG_REG_BITSTREAM_SIZE) = job->bitstream_size;
RS_JPEG_REG(RS_JPEG_REG_PLANE0_ADDR) = (uint32_t)job->planes[0];
RS_JPEG_REG(RS_JPEG_REG_PLANE0_STRIDE) = job->strides[0];
RS_JPEG_REG(RS_JPEG_REG_PLANE1_ADDR) = (uint32_t)job->planes[1];
RS_JPEG_REG(RS_JPEG_REG_PLANE1_STRIDE) = job->strides[1];
RS_JPEG_REG(RS_JPEG_REG_PLANE2_ADDR) = (uint32_t)job->planes[2];
RS_JPEG_REG(RS_JPEG_REG_PLANE2_STRIDE) = job->strides[2];
RS_JPEG_REG(RS_JPEG_REG_METADATA_ADDR) = (uint32_t)job->metadata;
RS_JPEG_REG(RS_JPEG_REG_METADATA_LENGTH) = job->metadata_length;
return RS_OK;
}
rs_status_t rs_jpeg_start(void) {
if ((RS_JPEG_REG(RS_JPEG_REG_STATUS) & RS_JPEG_STATUS_BUSY) != 0U) {
return RS_EIO;
}
RS_JPEG_REG(RS_JPEG_REG_IRQ_STATE) = RS_JPEG_IRQ_JOB_DONE | RS_JPEG_IRQ_ERROR;
RS_JPEG_REG(RS_JPEG_REG_COMMAND) = RS_JPEG_COMMAND_START;
return RS_OK;
}
rs_status_t rs_jpeg_wait(rs_timeout_t timeout) {
while (timeout-- != 0U) {
const uint32_t irq_state = RS_JPEG_REG(RS_JPEG_REG_IRQ_STATE);
if ((irq_state & RS_JPEG_IRQ_ERROR) != 0U) {
return RS_EIO;
}
if ((irq_state & RS_JPEG_IRQ_JOB_DONE) != 0U) {
return RS_OK;
}
}
return RS_ETIMEOUT;
}
rs_status_t rs_jpeg_abort(rs_timeout_t timeout) {
if ((RS_JPEG_REG(RS_JPEG_REG_STATUS) & RS_JPEG_STATUS_BUSY) == 0U) {
return RS_EINVAL;
}
RS_JPEG_REG(RS_JPEG_REG_IRQ_STATE) = RS_JPEG_IRQ_ABORT_DONE;
RS_JPEG_REG(RS_JPEG_REG_COMMAND) = RS_JPEG_COMMAND_ABORT;
while (timeout-- != 0U) {
if ((RS_JPEG_REG(RS_JPEG_REG_IRQ_STATE) & RS_JPEG_IRQ_ABORT_DONE) != 0U) {
return RS_OK;
}
}
return RS_ETIMEOUT;
}
rs_status_t rs_jpeg_get_status(rs_jpeg_status_t *status) {
uint32_t flags;
uint32_t image_size;
if (status == NULL) {
return RS_EINVAL;
}
flags = RS_JPEG_REG(RS_JPEG_REG_STATUS);
image_size = RS_JPEG_REG(RS_JPEG_REG_RESULT_IMAGE_SIZE);
status->busy = (flags & RS_JPEG_STATUS_BUSY) != 0U;
status->ring_active = (flags & RS_JPEG_STATUS_RING_ACTIVE) != 0U;
status->encode = (flags & RS_JPEG_STATUS_ENCODE) != 0U;
status->irq_state = RS_JPEG_REG(RS_JPEG_REG_IRQ_STATE);
status->error_status = RS_JPEG_REG(RS_JPEG_REG_ERROR_STATUS);
status->error_address = (uintptr_t)RS_JPEG_REG(RS_JPEG_REG_ERROR_ADDRESS);
status->result_size = RS_JPEG_REG(RS_JPEG_REG_RESULT_SIZE);
status->result_width = (uint16_t)image_size;
status->result_height = (uint16_t)(image_size >> 16U);
status->cycles = rs_jpeg_read_cycles();
status->input_bytes = RS_JPEG_REG(RS_JPEG_REG_INPUT_BYTES);
status->output_bytes = RS_JPEG_REG(RS_JPEG_REG_OUTPUT_BYTES);
return RS_OK;
}
rs_status_t rs_jpeg_irq_enable(uint32_t mask) {
if ((mask & ~RS_JPEG_IRQ_ALL) != 0U) {
return RS_EINVAL;
}
RS_JPEG_REG(RS_JPEG_REG_IRQ_ENABLE) = mask;
return RS_OK;
}
rs_status_t rs_jpeg_irq_pending(uint32_t *mask) {
if (mask == NULL) {
return RS_EINVAL;
}
*mask = RS_JPEG_REG(RS_JPEG_REG_IRQ_STATE) & RS_JPEG_IRQ_ALL;
return RS_OK;
}
rs_status_t rs_jpeg_irq_clear(uint32_t mask) {
if ((mask & ~RS_JPEG_IRQ_ALL) != 0U) {
return RS_EINVAL;
}
RS_JPEG_REG(RS_JPEG_REG_IRQ_STATE) = mask;
return RS_OK;
}
rs_status_t rs_jpeg_table_write(uint8_t context, uint8_t kind, uint8_t index, uint32_t value) {
if ((context > 3U) || (kind > 11U) ||
(((kind < 4U) && (index >= 64U)) || ((kind >= 4U) && (kind < 8U) && (index >= 12U)))) {
return RS_EINVAL;
}
if ((RS_JPEG_REG(RS_JPEG_REG_STATUS) & RS_JPEG_STATUS_BUSY) != 0U) {
return RS_EIO;
}
RS_JPEG_REG(RS_JPEG_REG_TABLE_CONTEXT) = (uint32_t)context;
RS_JPEG_REG(RS_JPEG_REG_TABLE_KIND) = (uint32_t)kind;
RS_JPEG_REG(RS_JPEG_REG_TABLE_INDEX) = (uint32_t)index;
RS_JPEG_REG(RS_JPEG_REG_TABLE_DATA) = value;
return RS_OK;
}
rs_status_t rs_jpeg_table_commit(uint8_t context) {
uint32_t status;
if (context > 3U) {
return RS_EINVAL;
}
RS_JPEG_REG(RS_JPEG_REG_TABLE_CONTEXT) = (uint32_t)context;
RS_JPEG_REG(RS_JPEG_REG_TABLE_COMMAND) = RS_JPEG_TABLE_COMMAND_COMMIT;
status = RS_JPEG_REG(RS_JPEG_REG_TABLE_STATUS);
return ((status & (RS_JPEG_TABLE_STATUS_VALID | RS_JPEG_TABLE_STATUS_ERROR)) ==
RS_JPEG_TABLE_STATUS_VALID)
? RS_OK
: RS_EIO;
}
rs_status_t rs_jpeg_table_clear(uint8_t context) {
if (context > 3U) {
return RS_EINVAL;
}
if ((RS_JPEG_REG(RS_JPEG_REG_STATUS) & RS_JPEG_STATUS_BUSY) != 0U) {
return RS_EIO;
}
RS_JPEG_REG(RS_JPEG_REG_TABLE_CONTEXT) = (uint32_t)context;
RS_JPEG_REG(RS_JPEG_REG_TABLE_COMMAND) = RS_JPEG_TABLE_COMMAND_CLEAR;
return RS_OK;
}
rs_status_t rs_jpeg_ring_init(rs_jpeg_descriptor_t *ring, uint32_t entries) {
if (rs_jpeg_ring_validate(ring, entries) != RS_OK) {
return RS_EINVAL;
}
if ((RS_JPEG_REG(RS_JPEG_REG_STATUS) & RS_JPEG_STATUS_BUSY) != 0U) {
return RS_EIO;
}
RS_JPEG_REG(RS_JPEG_REG_COMMAND) = RS_JPEG_COMMAND_SOFT_RESET;
RS_JPEG_REG(RS_JPEG_REG_RING_BASE) = (uint32_t)(uintptr_t)ring;
RS_JPEG_REG(RS_JPEG_REG_RING_SIZE) = entries;
RS_JPEG_REG(RS_JPEG_REG_RING_TAIL) = 0U;
RS_JPEG_REG(RS_JPEG_REG_RING_CONTROL) = RS_JPEG_RING_CONTROL_ENABLE;
return RS_OK;
}
rs_status_t rs_jpeg_ring_submit(uint32_t tail) {
const uint32_t entries = RS_JPEG_REG(RS_JPEG_REG_RING_SIZE);
if ((entries < 2U) || (tail >= entries)) {
return RS_EINVAL;
}
__asm__ volatile("fence rw, rw" ::: "memory");
RS_JPEG_REG(RS_JPEG_REG_RING_TAIL) = tail;
RS_JPEG_REG(RS_JPEG_REG_DOORBELL) = UINT32_C(1);
return RS_OK;
}
+144
View File
@@ -0,0 +1,144 @@
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <retrosoc/hal/jpeg.h>
static bool rs_jpeg_format_valid(rs_jpeg_pixel_format_t format) {
return (uint32_t)format <= (uint32_t)RS_JPEG_FORMAT_NV12;
}
static uint32_t rs_jpeg_minimum_stride(rs_jpeg_pixel_format_t format, uint16_t width) {
uint32_t result = (uint32_t)width;
if ((format == RS_JPEG_FORMAT_RGB565) || (format == RS_JPEG_FORMAT_YUYV422)) {
result *= 2U;
} else if (format == RS_JPEG_FORMAT_RGB888) {
result *= 3U;
} else if (format == RS_JPEG_FORMAT_NV12) {
result = (result + 1U) & ~UINT32_C(1);
} else {
/* GRAY8 uses one byte per pixel. */
}
return result;
}
static bool rs_jpeg_aligned(uintptr_t address, uintptr_t alignment) {
return (address & (alignment - 1U)) == 0U;
}
rs_status_t rs_jpeg_job_validate(const rs_jpeg_job_t *job) {
rs_jpeg_pixel_format_t surface_format;
uint32_t minimum_stride;
if ((job == NULL) ||
((job->mode != RS_JPEG_MODE_ENCODE) && (job->mode != RS_JPEG_MODE_DECODE)) ||
!rs_jpeg_format_valid(job->input_format) || !rs_jpeg_format_valid(job->output_format) ||
((uint32_t)job->sampling > (uint32_t)RS_JPEG_SAMPLING_420) || (job->width == 0U) ||
(job->height == 0U) || (job->width > RS_JPEG_MAX_DIMENSION) ||
(job->height > RS_JPEG_MAX_DIMENSION) || (job->table_context > 3U) ||
(job->bitstream == (uintptr_t)0U) || (job->bitstream_size == 0U) ||
!rs_jpeg_aligned(job->bitstream, RS_JPEG_DMA_ALIGNMENT)) {
return RS_EINVAL;
}
if ((job->mode == RS_JPEG_MODE_ENCODE) && ((job->quality == 0U) || (job->quality > 100U))) {
return RS_EINVAL;
}
if ((job->metadata != (uintptr_t)0U) || (job->metadata_length != 0U)) {
return RS_EINVAL;
}
surface_format = (job->mode == RS_JPEG_MODE_ENCODE) ? job->input_format : job->output_format;
minimum_stride = rs_jpeg_minimum_stride(surface_format, job->width);
if ((job->planes[0] == (uintptr_t)0U) ||
!rs_jpeg_aligned(job->planes[0], RS_JPEG_DMA_ALIGNMENT) ||
(job->strides[0] < minimum_stride)) {
return RS_EINVAL;
}
if (surface_format == RS_JPEG_FORMAT_NV12) {
if ((job->planes[1] == (uintptr_t)0U) ||
!rs_jpeg_aligned(job->planes[1], RS_JPEG_DMA_ALIGNMENT) ||
(job->strides[1] < minimum_stride)) {
return RS_EINVAL;
}
}
if ((job->bitstream > UINT32_MAX) || (job->planes[0] > UINT32_MAX) ||
(job->planes[1] > UINT32_MAX) || (job->planes[2] > UINT32_MAX) ||
(job->metadata > UINT32_MAX)) {
return RS_EINVAL;
}
return RS_OK;
}
rs_status_t rs_jpeg_descriptor_build(rs_jpeg_descriptor_t *descriptor, const rs_jpeg_job_t *job,
uint64_t cookie, bool interrupt) {
uint32_t control = RS_JPEG_DESCRIPTOR_OWN;
size_t index;
rs_status_t result;
if (descriptor == NULL) {
return RS_EINVAL;
}
result = rs_jpeg_job_validate(job);
if (result != RS_OK) {
return result;
}
if (interrupt) {
control |= RS_JPEG_DESCRIPTOR_IOC;
}
if (job->mode == RS_JPEG_MODE_ENCODE) {
control |= RS_JPEG_DESCRIPTOR_ENCODE;
}
if (job->auto_header) {
control |= RS_JPEG_DESCRIPTOR_AUTO_HEADER;
}
if (job->strict) {
control |= RS_JPEG_DESCRIPTOR_STRICT;
}
if (job->metadata_length != 0U) {
control |= RS_JPEG_DESCRIPTOR_METADATA;
}
control |= (uint32_t)job->table_context << RS_JPEG_DESCRIPTOR_TABLE_SHIFT;
control |= (uint32_t)job->input_format << RS_JPEG_DESCRIPTOR_INPUT_SHIFT;
control |= (uint32_t)job->output_format << RS_JPEG_DESCRIPTOR_OUTPUT_SHIFT;
control |= (uint32_t)job->sampling << RS_JPEG_DESCRIPTOR_SAMPLE_SHIFT;
descriptor->control = control;
descriptor->status = 0U;
descriptor->image_size = ((uint32_t)job->height << 16U) | (uint32_t)job->width;
descriptor->encode_config = (uint32_t)job->quality;
descriptor->restart_interval = (uint32_t)job->restart_interval;
descriptor->bitstream_addr = (uint32_t)job->bitstream;
descriptor->bitstream_size = job->bitstream_size;
descriptor->result_size = 0U;
descriptor->plane0_addr = (uint32_t)job->planes[0];
descriptor->plane0_stride = job->strides[0];
descriptor->plane1_addr = (uint32_t)job->planes[1];
descriptor->plane1_stride = job->strides[1];
descriptor->plane2_addr = (uint32_t)job->planes[2];
descriptor->plane2_stride = job->strides[2];
descriptor->metadata_addr = (uint32_t)job->metadata;
descriptor->metadata_length = job->metadata_length;
descriptor->cookie_lo = (uint32_t)cookie;
descriptor->cookie_hi = (uint32_t)(cookie >> 32U);
descriptor->result_image_size = 0U;
descriptor->result_format = 0U;
descriptor->cycles_lo = 0U;
descriptor->cycles_hi = 0U;
descriptor->input_bytes = 0U;
descriptor->output_bytes = 0U;
for (index = 0U; index < 8U; ++index) {
descriptor->reserved[index] = 0U;
}
return RS_OK;
}
rs_status_t rs_jpeg_ring_validate(const rs_jpeg_descriptor_t *ring, uint32_t entries) {
if ((ring == NULL) || !rs_jpeg_aligned((uintptr_t)ring, RS_JPEG_DESCRIPTOR_ALIGNMENT) ||
(entries < 2U) || (entries > RS_JPEG_RING_MAX_ENTRIES) ||
((entries & (entries - 1U)) != 0U)) {
return RS_EINVAL;
}
return RS_OK;
}
+5
View File
@@ -26,3 +26,8 @@ Buildroot source revisions. `make setup-hp-linux` installs the software sources
below `.cache/retrosoc/sources/`; VexiiRiscv may be supplied through
`VEXIIRISCV_ROOT`, but its revision is still checked before generated RTL is
accepted. No generated CPU RTL or Linux build output belongs in Git.
The libjpeg-turbo source archive is a host-verification input for the JPEG
accelerator. It supplies an implementation-independent interoperability oracle;
it is not linked into firmware or synthesized RTL. The repository-owned fixed
point model remains the bit-accurate source of expected RTL results.
+6
View File
@@ -95,6 +95,12 @@
"cluster_wdg": {"url": "https://github.com/retroSoC/wdg.git", "revision": "f5d39fa246054d678d34195824d943367dfcce44", "destination": "rtl/managed/clusterip/wdg", "license": "MulanPSL-2.0"}
},
"archives": {
"libjpeg_turbo": {
"url": "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/3.2.0/libjpeg-turbo-3.2.0.tar.gz",
"sha256": "6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e",
"destination": ".cache/retrosoc/downloads/libjpeg-turbo/libjpeg-turbo-3.2.0.tar.gz",
"license": "BSD-3-Clause AND IJG"
},
"ecc_cli_linux_x86_64": {
"url": "https://github.com/openecos-projects/ecc/releases/download/v0.1.0-alpha.10/ecc-cli-linux-x86_64.tar.gz",
"sha256": "fc3daaca24dddb04ba3490329042f52da05190da03c3831042293dd0cbffdca6",
+3
View File
@@ -43,6 +43,9 @@ the root README and subsystem guides.
HAL, verification evidence, and commercialization roadmap.
- [ip/usb2.md](ip/usb2.md) defines the ULPI USB 2.0 dual-role architecture,
AXI4 descriptor DMA, APB4 ABI, dedicated pads, HAL, and release gates.
- [ip/jpeg.md](ip/jpeg.md) records the commercial JPEG IP/SoC survey and
defines the Mini Baseline JPEG codec, private AXI4 DMA, APB4/ring ABI, HAL,
verification boundary, and measured 1080p60 optimization plan.
- [ip/mini-npu.md](ip/mini-npu.md) evaluates commercial edge-AI SoCs and records the
optional Mini-AI NPU architecture direction, memory constraints, software
boundary, and integration prerequisites; it is not an implemented profile.
+3
View File
@@ -19,6 +19,9 @@ extension interfaces.
- [i2s.md](i2s.md) defines the APB4 I2S master transceiver.
- [usb2.md](usb2.md) defines the dual-role ULPI USB 2.0 controller, AXI4
descriptor DMA, dedicated-pad integration, HAL, and commercial release gates.
- [jpeg.md](jpeg.md) defines the Baseline JPEG encoder/decoder, commercial
reference survey, APB4/AXI4 DMA and SG-ring ABI, HAL, verification evidence,
measured performance baseline, and 1080p60 optimization gates.
- [mini-npu.md](mini-npu.md) records the commercial NPU reference survey and optional
Mini-AI architecture direction; it does not define implemented RTL or ABI.
- [sdram.md](sdram.md) defines the AXI4 SDRAM data controller and APB4 configuration window.
+250
View File
@@ -0,0 +1,250 @@
# Baseline JPEG Codec
## Scope and Sources of Truth
The Mini SoC JPEG block is an 8-bit Baseline Sequential encoder/decoder with a
handwritten APB4 ABI, a private 64-bit AXI4 DMA master, direct jobs, a
scatter-gather ring, interrupts, and LP/HP transferable resource ownership. It
is mapped at `0x1001a000..0x1001afff` and supports images up to 2048 x 2048.
Encode and decode are mutually exclusive in one instance.
The executable sources of truth are:
- `rtl/ip/multimedia/jpeg_*.sv`, `apb4_jpeg.sv`, and `jpeg_define.svh`;
- `crt/include/retrosoc/hal/jpeg_regs.h`, `jpeg.h`, and
`crt/src/hal/jpeg*.c`;
- `rtl/mini/address_map/memory_map.json` and
`rtl/mini/integration/soc_topology.json`;
- `tests/jpeg_reference.py`, `tests/test_jpeg*.py`, and
`tests/rtl/jpeg_*_tb.sv`.
There is intentionally no register generator. The aligned SystemVerilog macro
table and C header are maintained manually. The parity test compares offsets,
field bits, shifts, and the fixed 128-byte descriptor size.
## Commercial Reference Survey
This survey was refreshed on 2026-08-31 from vendor, upstream Linux, and
standards material. "Active" means a current product or maintained upstream
driver was visible at review time. Public sources do not expose vendor RTL or
confidential PPA and verification reports.
| Reference | Problem and architecture | Dependencies | Activity | Reuse / avoid |
| --- | --- | --- | --- | --- |
| NXP i.MX 8QuadMax/8QuadXPlus JPEG-E-X/JPEG-D-X | Standalone CAST encode/decode cores sit behind wrappers. Configuration streams and pixels are fetched from memory, and chained descriptors feed four independently enabled bitstream slots. The Linux interface is V4L2 memory-to-memory. | AXI memory, coherent descriptor/configuration buffers, IRQs, V4L2 driver, CAST core integration. | Active: NXP still publishes the [i.MX 8 family documentation](https://www.nxp.com/products/i.MX8), and the [upstream driver](https://github.com/torvalds/linux/blob/master/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c) supports 8/12-bit raw formats, descriptor slots, IRQ recovery, NV12, YUYV, RGB, and grayscale. | Reuse separate configuration/data phases, memory descriptors, format negotiation, and interrupt recovery. Avoid fixed slot count as the public queue ABI, silent alignment padding, and driver assumptions that hide hardware limits. |
| CAST JPEG-E-S/JPEG-D-S and companion DMA | Autonomous Baseline JPEG cores use AXI4-Stream data and APB control. Optional raster-to-block conversion, metadata input, rate control, and AXI4 SGDMA form a complete subsystem. The encoder is specified at one sample/cycle and about 70k gates. | Streaming buffers, raster line/block conversion, optional DMA and rate-control products, licensed BAM and integration kit. | Active: the current [CAST JPEG family](https://www.cast-inc.com/compression/jpeg-image-compression) and [JPEG-E-S page](https://www.cast-inc.com/compression/jpeg-image-compression/jpeg-e-s) list current interfaces and deliverables. | Reuse APB plus AXI4-Stream partitioning, autonomous program-once operation, metadata side stream, BAM, self-checking testbench, and explicit DMA option. Avoid claiming optional rate control or one-sample/cycle performance without the matching implementation and measurement. |
| Alma Technologies JPEG-E-X/JPEG-D-X/UHT | A self-contained pipeline includes DCT, zig-zag/quantization, RLE/Huffman, syntax, tables, and optional video rate control. Standard cores expose flow-controlled streaming; UHT variants scale lanes for 4K/8K. | Streaming I/O, table/configuration interface, optional rate controller, vendor BAM and target scripts. | Active: Alma lists a current [JPEG IP portfolio](https://www.alma-technologies.com/ip-core.JPEG-IP) and an [AXI4-Stream JPEG-E-X](https://www.alma-technologies.com/ip-core.JPEG-E-X) with BAM, testbench, and ASIC scripts. | Reuse stage boundaries, independent table memories, flow control, positive-edge-only implementation, and versioned delivery artifacts. Avoid coupling the baseline MVP to 10/12-bit, multi-scan, DICOM, or rate control before baseline coverage closes. |
| VeriSilicon Hantro VC9000/VC9800 and Nano | A firmware-driven multi-format VPU shares scalable encode/decode infrastructure across modern video formats and JPEG. Nano variants target battery-powered products; larger variants scale cores and streams. | Embedded VPU processor/firmware, frame buffers, scheduler, driver stack, and often proprietary validation tools. | Active: the current [Hantro VPU portfolio](https://verisilicon.com/en/IPPortfolio/HantroVPUIP) lists JPEG in encode/decode products from Nano through 8K/multi-stream variants. | Reuse product variants, clock/power isolation, robust multi-context scheduling, and firmware/driver release discipline. Avoid importing a firmware CPU and multi-codec control plane into a small standalone JPEG accelerator. |
| Chips&Media CODA/WAVE-J class | CODA combines JPEG with video codecs behind job scheduling and memory buffers; the current product family separates JPEG/CODA from WAVE generations. Linux exposes CODA as V4L2 memory-to-memory. | Codec firmware on some variants, DMA memory, V4L2, shared VPU scheduling and recovery. | Active: Chips&Media retains a [JPEG/CODA product category](https://chipsnmedia.com/en/products/index), and current Linux contains maintained CODA/WAVE drivers. | Reuse memory-to-memory software semantics, context ownership, timeout/reset recovery, and buffer completion rules. Avoid making JPEG availability implicit in a broader VPU product name or requiring opaque firmware for a baseline-only block. |
| ASPEED AST video JPEG | A capture-oriented JPEG engine serves BMC remote-display workloads. The upstream driver prebuilds several quality tables and supports standard and ASPEED-specific JPEG, 4:2:0/4:4:4 selection, and up to 1920 x 1200 at 60 Hz input timing. | Display capture engine, reserved/coherent buffers, fixed quality presets, V4L2 and BMC display pipeline. | Active in the current [upstream ASPEED video driver](https://github.com/torvalds/linux/blob/master/drivers/media/platform/aspeed/aspeed-video.c). | Reuse precomputed table contexts and encode-many operation. Avoid binding a general JPEG IP to one capture source, vendor-specific stream headers, or a small fixed quality set. |
The highest-value combination for retroSoC is the CAST/Alma streaming core
boundary plus the NXP descriptor wrapper: keep the codec stages independently
testable, then add a native AXI4 raster/bitstream DMA and a queue. Hantro's
firmware VPU is deliberately not selected because its multi-codec flexibility
costs more area, software, and verification than this product needs.
## Selected Architecture
```text
APB4 @ 0x1001a000
|
+---------------- register / IRQ / first-error ----------------+
| | |
table portal direct job context SG ring context
4 contexts raster/stream addresses 128-byte descriptors
| | |
+-------------------------+------------------+-----------------+
|
raster memory <-> 64-bit AXI4 DMA <-> MCU build/reconstruct
|
+----------------------+----------------------+
| |
encode: RGB/YCbCr -> FDCT -> quant -> RLE/Huffman -> pack/syntax
decode: marker parser -> Huffman/RLE -> dequant -> IDCT -> RGB/YCbCr
| |
+--------------- JPEG bitstream ---------------+
```
The control block and codec run on PCLK. A Common `axi4_async_bridge` crosses
the JPEG master into the HP AXI64 data plane. The master can read and write
independently, uses incrementing bursts of up to 16 beats, splits at 4 KiB,
honors byte strobes on tails, and drains accepted traffic on abort. It uses
data-master index 6 and can access SRAM, SDRAM, QPI, OPI, and XPI for reads;
writes exclude XPI. All JPEG buffers are non-cacheable/shared or explicitly
cleaned/invalidated by the owner.
The Resource Controller exposes JPEG as resource 6. Ownership can move between
LP and HP only after quiesce, DMA drain, and idle acknowledgement. The LP
aggregate source is peripheral group bit 22/core IRQ30; HP PLIC source 9 is
used while HP owns the resource. Reset aborts the codec and drains the AXI
master before idle is reported.
## Implemented MVP
| Area | Implemented behavior | Current limit |
| --- | --- | --- |
| JPEG syntax | 8-bit SOF0 Baseline Sequential, JFIF header generation, DQT, DHT, DRI, SOS, EOI, byte stuffing, and RST0..RST7. | No progressive, arithmetic, lossless, hierarchical, 10/12-bit, multi-scan, or abbreviated-table stream. |
| Encode | Grayscale, 4:4:4, 4:2:2, and 4:2:0; programmable quant/Huffman contexts and restart interval. | Hardware always produces a complete JFIF stream. APP/COM metadata and rate control are rejected, not ignored. |
| Decode | Strict marker parser, in-stream DQT/DHT table expansion, restart checking, edge replication, and complete raster output. | One to three components using the supported sampling patterns; malformed/unsupported streams fail closed. |
| Raster formats | `GRAY8`, `RGB565`, `RGB888`, `YUYV422`, and two-plane `NV12`, with independent byte strides. | 8-bit samples only; no planar I420/YV12, BGR/ARGB, tiled, compressed, or 10/12-bit surfaces. |
| Scheduling | Direct job and 2..256-entry power-of-two SG ring; descriptor OWN/IOC, result writeback, stop-on-error, coalescing, head/tail and doorbell. | One active codec job. No preemption, priority queue, virtual contexts, IOMMU, or hardware cache snoop. |
| Tables | Four encoder contexts in six 1024 x 32 technology SRAMs. Decoder tables come from the input JPEG. Portal writes auto-increment and commit atomically marks a context valid. | Software must load expanded canonical Huffman tables. `TABLE_DEFAULT` is reserved and marks the context erroneous; quality is realized by the loaded quant tables. |
| Faults | APB alignment/access errors, validation errors, first AXI fault address/response, malformed JPEG codes, abort, resource reset, sticky W1C IRQ and first-error state. | Error recovery is job/ring level; no ECC/MBIST on table storage and no duplicated safety controller. |
The locked libjpeg-turbo 3.2.0 archive is a host-verification input, not linked
into freestanding firmware. `tests/jpeg_reference.py` is the deterministic BAM
used for byte-exact RTL vectors; libjpeg-turbo/Pillow interoperability provides
an independent ecosystem check. The archive URL and SHA-256 are controlled by
`dependencies/dependencies.lock.json`.
## Data Contract
- Images are 1..2048 pixels in each dimension. Partial edge MCUs replicate
the final valid row/column; callers do not pad the image.
- DMA addresses are 8-byte aligned. Stride is bytes and must cover one logical
raster line. NV12 plane 1 contains interleaved Cb/Cr pairs.
- RGB565 words are little-endian `rrrrrggg gggbbbbb`. RGB888 byte order is R,
G, B. YUYV is Y0, Cb, Y1, Cr. AXI lane 0 is the earliest byte.
- Encode uses table IDs 0 and 1 from `JOB_CONFIG.TABLE_CONTEXT`. Table kinds
0..3 are 64-entry quant tables, 4..7 are 12-entry DC code/length tables, and
8..11 are 256-entry AC code/length tables. Huffman words contain code
`[15:0]` and length `[20:16]`.
- Descriptor and raster memory must not be modified while hardware owns it.
Software writes descriptor payload, sets OWN last, executes `fence rw,rw`,
advances tail, and rings the doorbell. Hardware writes results and clears
OWN before advancing head and raising the event.
- `ABORT` and resource reset stop new work but never withdraw an accepted AXI
transfer. Completion is reported only after read/write channels drain.
## Register ABI
All registers are 32-bit, word aligned, and byte-strobe aware. Configuration
writes are rejected while busy, except `RING_TAIL` and the doorbell so a
producer can extend a running queue.
| Offset | Register | Access | Purpose |
| ---: | --- | --- | --- |
| `0x000..0x00c` | `IP_ID`, `IP_VERSION`, `CAPABILITY0/1` | RO | Identification and implemented feature geometry |
| `0x010` | `COMMAND` | WO | Start, abort, soft reset, or ring kick |
| `0x014` | `STATUS` | RO | Busy, ring active, encode, idle |
| `0x018..0x020` | `IRQ_STATE/ENABLE/TEST` | RO/W1C, RW, WO | Job, ring, header, abort, and error IRQs |
| `0x024..0x02c` | `ERROR_STATUS/ADDRESS/DETAIL` | RO/W1C | Sticky first error, stage, AXI response, and address |
| `0x030..0x04c` | performance registers | RW/RO | Enable/clear, cycles, pixels, bytes, read/write stalls |
| `0x080..0x094` | job/image/format/encode/restart | RW idle | Direct-mode operation and image geometry |
| `0x098..0x0bc` | bitstream, plane, metadata addresses | RW idle | DMA addresses, sizes, and strides; metadata is reserved |
| `0x0c0..0x0cc` | result registers | RO | Byte count, decoded image size/format, parsed markers |
| `0x100..0x11c` | ring registers | RW/RO | Base, size, head, tail, enable/stop, status, coalescing, doorbell |
| `0x200..0x214` | table portal | RW/RO | Context, kind, index, data, command, status |
`IRQ_STATE` bits 0..4 are job done, ring event, header ready, abort done,
and error. `ERROR_STATUS` packs valid bit 0, error code `[5:1]`, stage `[9:6]`,
and AXI response `[11:10]`. Defined codes include invalid job 1, invalid ring
2, table context 3, marker 4, unsupported process 5, precision 6, sampling 7,
length 8, Huffman 9, truncated stream 10, restart 11, AXI read 13, AXI write
14, and datapath/overflow 18. Unknown codes must be treated as fatal for the
current job.
## Descriptor ABI
Each descriptor is 128-byte aligned and exactly 128 bytes. Ring size is a
power of two from 2 through 256. Head is hardware owned; tail is software
owned.
| Word | Field | Direction |
| ---: | --- | --- |
| 0 | OWN, IOC, encode, auto-header, strict, metadata, table/input/output/sampling | SW -> HW; HW clears OWN |
| 1 | done/error/abort and error code | HW -> SW |
| 2..6 | image, encode config, restart, bitstream address/size | SW -> HW |
| 7 | compressed/output result size | HW -> SW |
| 8..15 | three plane addresses/strides and metadata address/length | SW -> HW |
| 16..17 | opaque 64-bit cookie | Preserved |
| 18..23 | result image/format, cycles, input/output bytes | HW -> SW |
| 24..31 | reserved, write zero | Reserved |
An invalid owned descriptor is completed with error, written back, and head
advances. An unowned descriptor stalls the ring without consuming it. IOC
overrides coalescing. `STOP_ERROR` prevents fetching the next descriptor after
an error writeback.
## Performance Target and Optimization Plan
The product target is 1920 x 1080 4:2:0 at 60 frames/s on a 72 MHz codec
clock. This is 124.416 Mpixel/s and 186.624 M color samples/s, so a sustained
implementation needs at least 1.728 pixels/cycle or 2.592 color samples/cycle,
before blanking and memory stalls.
The current functional pipeline does not meet that target. The deterministic
Icarus benchmark measured 979 busy cycles for one 16 x 16 4:2:0 MCU and 1347
for two, or about 368 steady-state cycles/MCU. Excluding DMA stalls, that
projects to about 3.00 million cycles for 1080p, approximately 24 fps at
72 MHz. This is an optimization baseline, not a 1080p60 claim.
The commercial-performance revision is ordered as follows:
1. Add two ping-pong MCU raster buffers so DMA captures MCU N+1 while N is
transformed. Coalesce compressed output into 16-beat writes instead of one
AXI transaction per output beat.
2. Split transform, quantize, and entropy into independently backpressured
block stages. Bank block storage and carry component/DC metadata beside the
data so at least one block enters every eight cycles.
3. Use four transform lanes and two entropy lanes for the 4:2:0 profile. Compute
quantized DC values before entropy issue so luma predictor dependencies do
not serialize DCT/quant work. Preserve token order through per-block FIFOs.
4. Convert RGB/YUYV/NV12 with at least two pixels/cycle and bank the raster
buffer to supply four color samples/cycle. Register multiplier outputs and
fanout-heavy table selects before physical synthesis.
5. Mirror the pipeline on decode: speculative Huffman lookahead, four
coefficients/cycle inverse RLE, overlapped dequant/IDCT, and ping-pong
reconstruction. Invalid codes still retire in order and fail closed.
6. Gate the claim with a 1080p frame simulation using randomized AXI
backpressure, `CYCLES <= 1,200,000` at zero memory stall, sustained SDRAM
bandwidth evidence, and post-layout timing/power. A frequency-only result
cannot satisfy this gate.
## Verification and Commercial Delivery Alignment
| Delivery area | Current evidence | Commercial release gate |
| --- | --- | --- |
| Standards and BAM | T.81-derived deterministic model; byte-exact complete-file encode; decoded pixels match BAM; Pillow interoperability. | Full libjpeg-turbo encode/decode differential matrix, JPEG conformance corpus, malformed/adversarial corpus, all restart positions and marker permutations. |
| Block/datapath | FDCT/IDCT, quant/dequant, Huffman, stuffing, marker, MCU format, edge and complete-file directed tests. | Randomized coefficient extremes, saturation proof, PSNR/error bounds by quality, functional coverage and long randomized images. |
| APB/AXI/ring | APB error/IRQ/table tests, 2D DMA burst/4 KiB/tail test, and APB+AXI descriptor fetch/error-writeback/head/IRQ test. | Constrained-random AXI VIP, simultaneous read/write backpressure, all response faults, ring wrap/full/producer races, abort/reset at every state, coherency tests. |
| SoC lifecycle | Generated address/IRQ/resource topology, dedicated AXI64 CDC bridge, quiesce/reset drain, RTL/C parity. | LP/HP live ownership transfer with cache maintenance, clock-stop/reset matrix, CDC/RDC signoff and isolation fault injection. |
| PPA/performance | Cycle counters, reproducible 1/2-MCU baseline, IHP130 synthesis/STA hooks. | Implement the multi-lane revision, 1080p60 frame gate, PVT/MMMC closure, power/activity report and post-layout memory bandwidth. |
| Release package | Synthesizable RTL, handwritten HAL/ABI, Python BAM, directed tests, dependency lock, architecture document. | Versioned requirements traceability, UVM/VIP environment, coverage reports, SVA/formal pack, lint/CDC/RDC/DFT reports, gate/SDF tests, release notes and integration example. |
Current formal gaps are explicit: there is no end-to-end equivalence proof,
CDC/RDC signoff, fault-injection campaign, SDF test, ECC/MBIST, or ISO
conformance certification. Metadata insertion, rate control, 10/12-bit data,
progressive decode, and multi-instance scaling are feature expansions after
the 1080p60 baseline closes. APP/COM insertion should use a bounded metadata
AXI4-Stream side input and marker allowlist; it must not let software splice
unvalidated bytes into entropy data.
## Development Order
The MVP implementation order is the required order for future changes:
1. Freeze syntax, raster ordering, APB fields, descriptor ownership, error and
abort semantics.
2. Extend the BAM and independent library differential tests before RTL.
3. Verify transform, quantization, entropy and marker primitives separately.
4. Integrate complete encode/decode streams, then raster conversion and edge
handling.
5. Add 2D AXI DMA, direct operation, ring fetch/writeback, IRQ and resource
lifecycle tests.
6. Integrate the SoC address, AXI CDC, ACL, resource and PLIC routing.
7. Run style, software policy, host/Python tests, firmware, behavioral
simulation, IHP130 synthesis, netlist simulation, STA, warning and metric
gates. Optimize only after a measured failing stage is identified.
## Standards and Golden Model
- [ITU-T T.81 / ISO/IEC 10918-1](https://www.itu.int/ITU-T/recommendations/rec.aspx?id=2633)
is the normative JPEG-1 baseline.
- The public [T.81 common text](https://www.w3.org/Graphics/JPEG/itu-t81.pdf)
is used for marker, entropy, restart and DCT behavior.
- [libjpeg-turbo 3.2.0](https://github.com/libjpeg-turbo/libjpeg-turbo/releases/tag/3.2.0)
is the locked active independent implementation. Its known restart-marker
slow path is a software implementation detail, not a reason to omit restart
verification in hardware.
+2 -1
View File
@@ -22,7 +22,8 @@
| HP normal and forced hot reset | AON lifecycle controller and Resource Controller | cache request/ACK, drain, timeout, flush, and recovery tests | implemented digitally: Zicbom and bounded ACK window exist; range policy and Linux service remain software work |
| Clock monitor and programmable timeout | AON clock/reset subsystem | 8x8 DFS and clock-loss tests | implemented |
| Operational EXT-H ACL, timeout, and data path | Extension subsystem | transfer, denial, hang, and quiesce tests | implemented |
| Ownership-aware IRQ handoff | Resource Controller and HP PLIC | idle handoff, owner lock, LP/HP exclusion, and fault tests | implemented for DMA, USB2, SDIO0/1, SPI-SD, and EXT-H; downstream per-engine reset ACK remains pending |
| Ownership-aware IRQ handoff | Resource Controller and HP PLIC | idle handoff, owner lock, LP/HP exclusion, and fault tests | implemented for DMA, USB2, SDIO0/1, SPI-SD, EXT-H, and JPEG; downstream per-engine reset ACK remains pending |
| JPEG 1080p60 at 72 MHz | JPEG codec and private AXI4 DMA | complete 1080p encode/decode cycles, randomized AXI stalls, synthesis/STA/power | partial: functional direct/ring codec and cycle benchmark exist; current encode core projects to about 24 fps, and the required multi-lane pipeline remains planned |
| Bounded target timeout and isolation | Target guards | pre-/post-accept timeout, burst completion, and late-response isolation | implemented: synthetic SLVERR and fail-closed isolation; physical fault injection pending |
| Root-visible fabric observability | Fabric Monitor | counters, snapshot, high-water, promotion, timeout, isolation, flush, sticky fault, and RTL/C parity tests | implemented; histogram/interrupt/trace streaming and silicon correlation remain pending |
| QPI/OPI pad exclusion and inactive-window error | Memory pad manager | simulation and formal mutual-exclusion proof | implemented |
+14
View File
@@ -101,6 +101,20 @@ use APB4-peripheral group bit 14 and management-core IRQ20. Their stream
selection, backpressure, and transfer-boundary rules are defined in
[axi4-stream.md](axi4-stream.md).
## JPEG codec integration
The Baseline JPEG codec occupies APB4 slot 26 at `0x1001a000`. It owns a
private 64-bit AXI4 master rather than consuming central-DMA channels. The
master is index 6 in the generated data-plane policy and crosses from PCLK to
the HP fabric through `u_jpeg_cdc`; its buffers must be non-cacheable/shared or
covered by the Resource Controller cache-maintenance handoff.
JPEG is resource index 6. Its LP interrupt is APB4-peripheral group bit 22 and
management-core IRQ30; HP ownership routes the raw source to PLIC source 9.
Quiesce blocks new direct/ring jobs, resource reset aborts the codec, and idle
is acknowledged only after both AXI channels drain. The APB4/raster/ring ABI
and performance boundary are defined in [jpeg.md](ip/jpeg.md).
## OPI PSRAM alternate functions
The octal PSRAM controller uses GPIO21-31 ALT0 for CK, CS#, DQ[7:0], and
+7
View File
@@ -112,6 +112,13 @@ RSA-2048 Montgomery exponentiation. Its register ABI, key/zeroize boundary,
commercial survey, and verification roadmap are documented in
[AES/SHA-2/RSA Crypto Controller](../docs/ip/crypto.md).
The self-owned JPEG controller provides 8-bit Baseline Sequential encode and
decode, five raster formats, a 64-bit AXI4 2D DMA, direct and 128-byte SG-ring
jobs, four encoder table contexts, interrupts, and LP/HP transferable resource
ownership. Its implemented limits, handwritten ABI, measured performance, and
commercial release gates are documented in
[Baseline JPEG Codec](../docs/ip/jpeg.md).
SystemCtrl uses `sysctrl_if.sv`, `sysctrl_define.svh`, `sysctrl_reg.sv`, and
`sysctrl_core.sv` behind the stable `apb4_sysctrl` integration wrapper. Its
generated register offsets, APB4 timing, control-plane behavior, and
File diff suppressed because it is too large. Load diff
+229
View File
@@ -0,0 +1,229 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_bit_packer #(
parameter int unsigned TokenSlots = 4,
parameter int unsigned TokenWidth = 32,
parameter int unsigned OutputBytes = 8,
parameter int unsigned ReservoirWidth = 256
) (
// verilog_format: off -- preserve token, flush, and byte-stream interface columns
input logic clk_i,
input logic rst_n_i,
input logic [TokenSlots*TokenWidth-1:0] token_bits_i,
input logic [TokenSlots*6-1:0] token_length_i,
input logic [$clog2(TokenSlots+1)-1:0] token_count_i,
input logic token_valid_i,
output logic token_ready_o,
input logic flush_i,
output logic flush_ready_o,
output logic flush_done_o,
output logic [OutputBytes*8-1:0] byte_data_o,
output logic [OutputBytes-1:0] byte_keep_o,
output logic byte_valid_o,
output logic byte_last_o,
input logic byte_ready_i,
output logic error_o
// verilog_format: on
);
typedef enum logic {
Running,
Flushing
} state_e;
localparam int unsigned CountWidth = $clog2(ReservoirWidth + 1);
localparam int unsigned TokenCountWidth = $clog2(TokenSlots + 1);
state_e s_state_d;
state_e s_state_q;
logic s_state_bits_q;
logic [ ReservoirWidth-1:0] s_reservoir_d;
logic [ ReservoirWidth-1:0] s_reservoir_q;
logic [ CountWidth-1:0] s_bit_count_d;
logic [ CountWidth-1:0] s_bit_count_q;
logic s_stuff_pending_d;
logic s_stuff_pending_q;
logic s_empty_flush_d;
logic s_empty_flush_q;
logic s_err_d;
logic s_err_q;
logic [ ReservoirWidth-1:0] s_output_reservoir;
logic [ CountWidth-1:0] s_output_count;
logic s_output_stuff_pending;
logic [$clog2(OutputBytes+1)-1:0] s_output_byte_count;
logic [ 7:0] s_output_byte;
logic [ CountWidth-1:0] s_input_bit_count;
logic [ ReservoirWidth-1:0] s_append_reservoir;
logic [ CountWidth-1:0] s_append_count;
logic [ 5:0] s_token_length;
logic [ TokenWidth-1:0] s_token_bits;
function automatic logic [ReservoirWidth-1:0] expanded_token(input logic [TokenWidth-1:0] bits_i,
input logic [5:0] length_i);
logic [TokenWidth-1:0] s_mask;
begin
s_mask = (TokenWidth'(1) << length_i) - 1'b1;
return ReservoirWidth'(bits_i & s_mask);
end
endfunction
always_comb begin
s_input_bit_count = '0;
for (int unsigned token = 0; token < TokenSlots; token++) begin
if (token < token_count_i) begin
s_input_bit_count += CountWidth'(token_length_i[token*6+:6]);
end
end
end
assign s_state_q = state_e'(s_state_bits_q);
assign byte_valid_o = (s_bit_count_q >= CountWidth'(OutputBytes * 8)) ||
((s_state_q == Flushing) &&
((s_bit_count_q != '0) || s_stuff_pending_q));
assign token_ready_o = (s_state_q == Running) && !flush_i &&
(s_input_bit_count <= CountWidth'(ReservoirWidth) - s_bit_count_q);
assign flush_ready_o = (s_state_q == Running) && !token_valid_i;
assign flush_done_o = s_empty_flush_q || (byte_valid_o && byte_ready_i && byte_last_o);
assign error_o = s_err_q;
always_comb begin
byte_data_o = '0;
byte_keep_o = '0;
s_output_reservoir = s_reservoir_q;
s_output_count = s_bit_count_q;
s_output_stuff_pending = s_stuff_pending_q;
s_output_byte_count = '0;
s_output_byte = '0;
for (int unsigned lane = 0; lane < OutputBytes; lane++) begin
if (s_output_stuff_pending) begin
byte_data_o[lane*8+:8] = 8'h00;
byte_keep_o[lane] = 1'b1;
s_output_stuff_pending = 1'b0;
s_output_byte_count += 1'b1;
end else if (s_output_count >= CountWidth'(8)) begin
s_output_byte = 8'(s_output_reservoir >> (s_output_count - 8));
byte_data_o[lane*8+:8] = s_output_byte;
byte_keep_o[lane] = 1'b1;
s_output_count -= CountWidth'(8);
if (s_output_byte == 8'hff) begin
s_output_stuff_pending = 1'b1;
end
s_output_byte_count += 1'b1;
end
end
byte_last_o = (s_state_q == Flushing) && (s_output_count == '0) &&
!s_output_stuff_pending && (s_output_byte_count != '0);
end
always_comb begin
s_state_d = s_state_q;
s_reservoir_d = s_reservoir_q;
s_bit_count_d = s_bit_count_q;
s_stuff_pending_d = s_stuff_pending_q;
s_empty_flush_d = 1'b0;
s_err_d = s_err_q;
s_append_reservoir = s_reservoir_q;
s_append_count = s_bit_count_q;
s_token_length = '0;
s_token_bits = '0;
if (byte_valid_o && byte_ready_i) begin
s_reservoir_d = s_output_reservoir;
s_bit_count_d = s_output_count;
s_stuff_pending_d = s_output_stuff_pending;
if (byte_last_o) begin
s_state_d = Running;
end
end
if (token_valid_i && token_ready_o) begin
s_append_reservoir = s_reservoir_d;
s_append_count = s_bit_count_d;
if ((token_count_i == '0) || (token_count_i > TokenCountWidth'(TokenSlots))) begin
s_err_d = 1'b1;
end
for (int unsigned token = 0; token < TokenSlots; token++) begin
if (token < token_count_i) begin
s_token_length = token_length_i[token*6+:6];
s_token_bits = token_bits_i[token*TokenWidth+:TokenWidth];
if ((s_token_length == 6'd0) || (s_token_length > 6'(TokenWidth))) begin
s_err_d = 1'b1;
end else begin
s_append_reservoir = (s_append_reservoir << s_token_length) |
expanded_token(s_token_bits, s_token_length);
s_append_count += CountWidth'(s_token_length);
end
end
end
s_reservoir_d = s_append_reservoir;
s_bit_count_d = s_append_count;
end
if (flush_i && flush_ready_o) begin
if (s_bit_count_d[2:0] != 3'd0) begin
s_token_length = 6'(8 - s_bit_count_d[2:0]);
s_reservoir_d = (s_reservoir_d << s_token_length) |
((ReservoirWidth'(1) << s_token_length) - 1'b1);
s_bit_count_d += CountWidth'(s_token_length);
end
if ((s_bit_count_d == '0) && !s_stuff_pending_d) begin
s_empty_flush_d = 1'b1;
s_state_d = Running;
end else begin
s_state_d = Flushing;
end
end
end
dffr #(
.DATA_WIDTH(1)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(ReservoirWidth)
) u_reservoir_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_reservoir_d),
.dat_o (s_reservoir_q)
);
dffr #(
.DATA_WIDTH(CountWidth)
) u_bit_count_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_bit_count_d),
.dat_o (s_bit_count_q)
);
dffr u_stuff_pending_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_stuff_pending_d),
.dat_o (s_stuff_pending_q)
);
dffr u_empty_flush_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_empty_flush_d),
.dat_o (s_empty_flush_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
`ifndef SYNTHESIS
initial begin
if (TokenSlots < 1 || TokenWidth < 27 || OutputBytes < 1 || ReservoirWidth < 128) begin
$fatal(1, "jpeg_bit_packer: invalid interface or reservoir width");
end
end
`endif
endmodule
+167
View File
@@ -0,0 +1,167 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_bit_reader #(
parameter int unsigned ReservoirWidth = 128,
parameter int unsigned WindowWidth = 32
) (
// verilog_format: off -- preserve byte, bit-window, and marker interface columns
input logic clk_i,
input logic rst_n_i,
input logic [7:0] byte_i,
input logic byte_valid_i,
output logic byte_ready_o,
input logic byte_last_i,
input logic [$clog2(WindowWidth+1)-1:0] bit_consume_i,
input logic bit_consume_valid_i,
input logic align_i,
output logic [WindowWidth-1:0] bit_window_o,
output logic [$clog2(WindowWidth+1)-1:0] bit_count_o,
output logic marker_valid_o,
output logic [7:0] marker_o,
input logic marker_ready_i,
output logic error_o
// verilog_format: on
);
localparam int unsigned CountWidth = $clog2(ReservoirWidth + 1);
localparam int unsigned WindowCountWidth = $clog2(WindowWidth + 1);
localparam logic [CountWidth-1:0] WindowCount = CountWidth'(WindowWidth);
localparam logic [CountWidth-1:0] CapacityCount = CountWidth'(ReservoirWidth - 8);
logic [ReservoirWidth-1:0] s_reservoir_d;
logic [ReservoirWidth-1:0] s_reservoir_q;
logic [ CountWidth-1:0] s_bit_cnt_d;
logic [ CountWidth-1:0] s_bit_cnt_q;
logic s_ff_pending_d;
logic s_ff_pending_q;
logic s_marker_valid_d;
logic s_marker_valid_q;
logic [ 7:0] s_marker_d;
logic [ 7:0] s_marker_q;
logic s_err_d;
logic s_err_q;
logic s_append_byte;
logic [ 7:0] s_append_value;
assign byte_ready_o = !s_marker_valid_q && (s_bit_cnt_q <= CapacityCount);
assign marker_valid_o = s_marker_valid_q;
assign marker_o = s_marker_q;
assign error_o = s_err_q;
always_comb begin
if (s_bit_cnt_q >= WindowCount) begin
bit_window_o = WindowWidth'(s_reservoir_q >> (s_bit_cnt_q - WindowCount));
bit_count_o = WindowCountWidth'(WindowWidth);
end else begin
bit_window_o = WindowWidth'(s_reservoir_q << (WindowCount - s_bit_cnt_q));
bit_count_o = WindowCountWidth'(s_bit_cnt_q);
end
end
always_comb begin
s_reservoir_d = s_reservoir_q;
s_bit_cnt_d = s_bit_cnt_q;
s_ff_pending_d = s_ff_pending_q;
s_marker_valid_d = s_marker_valid_q;
s_marker_d = s_marker_q;
s_err_d = s_err_q;
s_append_byte = 1'b0;
s_append_value = byte_i;
if (bit_consume_valid_i) begin
if (CountWidth'(bit_consume_i) > s_bit_cnt_d) begin
s_err_d = 1'b1;
end else begin
s_bit_cnt_d -= CountWidth'(bit_consume_i);
end
end
if (align_i) begin
s_bit_cnt_d = '0;
s_reservoir_d = '0;
end
if (s_marker_valid_q && marker_ready_i) begin
s_marker_valid_d = 1'b0;
end
if (byte_valid_i && byte_ready_o) begin
if (s_ff_pending_q) begin
if (byte_i == 8'h00) begin
s_append_byte = 1'b1;
s_append_value = 8'hff;
s_ff_pending_d = 1'b0;
end else if (byte_i == 8'hff) begin
s_ff_pending_d = 1'b1;
end else begin
s_marker_valid_d = 1'b1;
s_marker_d = byte_i;
s_ff_pending_d = 1'b0;
if (!((byte_i >= 8'hd0 && byte_i <= 8'hd7) || (byte_i == 8'hd9))) begin
s_err_d = 1'b1;
end
end
end else if (byte_i == 8'hff) begin
s_ff_pending_d = 1'b1;
end else begin
s_append_byte = 1'b1;
end
if (byte_last_i && (byte_i != 8'hd9)) begin
s_err_d = 1'b1;
end
end
if (s_append_byte) begin
s_reservoir_d = (s_reservoir_d << 8) | ReservoirWidth'(s_append_value);
s_bit_cnt_d += CountWidth'(8);
end
end
dffr #(
.DATA_WIDTH(ReservoirWidth)
) u_reservoir_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_reservoir_d),
.dat_o (s_reservoir_q)
);
dffr #(
.DATA_WIDTH(CountWidth)
) u_bit_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_bit_cnt_d),
.dat_o (s_bit_cnt_q)
);
dffr u_ff_pending_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_ff_pending_d),
.dat_o (s_ff_pending_q)
);
dffr u_marker_valid_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_marker_valid_d),
.dat_o (s_marker_valid_q)
);
dffr #(
.DATA_WIDTH(8)
) u_marker_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_marker_d),
.dat_o (s_marker_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
`ifndef SYNTHESIS
initial begin
if (ReservoirWidth < 64 || WindowWidth < 27 || WindowWidth > ReservoirWidth) begin
$fatal(1, "jpeg_bit_reader: invalid reservoir or window width");
end
end
`endif
endmodule
+227
View File
@@ -0,0 +1,227 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_block_decoder #(
parameter int unsigned ElementWidth = 24,
parameter int unsigned WindowWidth = 32
) (
// verilog_format: off -- preserve command, table, bit-window, and block columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic signed [ElementWidth-1:0] previous_dc_i,
input logic [64*8-1:0] quant_i,
input logic [64*25-1:0] reciprocal_i,
input logic [12*16-1:0] dc_code_i,
input logic [12*5-1:0] dc_length_i,
input logic [256*16-1:0] ac_code_i,
input logic [256*5-1:0] ac_length_i,
input logic [WindowWidth-1:0] bit_window_i,
input logic [$clog2(WindowWidth+1)-1:0] bit_count_i,
output logic [$clog2(WindowWidth+1)-1:0] bit_consume_o,
output logic bit_consume_valid_o,
output logic start_ready_o,
output logic [64*8-1:0] block_o,
output logic signed [ElementWidth-1:0] dc_o,
output logic result_valid_o,
input logic result_ready_i,
output logic error_o
// verilog_format: on
);
typedef enum logic [2:0] {
Idle,
Entropy,
Dequantize,
Transform,
Result
} state_e;
state_e s_state_d;
state_e s_state_q;
logic [ 2:0] s_state_bits_q;
logic signed [ ElementWidth-1:0] s_dc_d;
logic signed [ ElementWidth-1:0] s_dc_q;
logic [ 64*8-1:0] s_block_d;
logic [ 64*8-1:0] s_block_q;
logic s_err_d;
logic s_err_q;
logic s_entropy_start;
logic s_entropy_ready;
logic [64*ElementWidth-1:0] s_entropy_block;
logic signed [ ElementWidth-1:0] s_entropy_dc;
logic s_entropy_valid;
logic s_entropy_err;
logic s_quant_start;
logic s_quant_ready;
logic [64*ElementWidth-1:0] s_quant_block;
logic s_quant_valid;
logic s_quant_err;
logic s_quant_overflow;
logic s_transform_start;
logic s_transform_ready;
logic [64*ElementWidth-1:0] s_transform_block;
logic s_transform_valid;
logic [ 64*8-1:0] s_clamped_block;
function automatic logic [7:0] clamp_sample(input logic signed [ElementWidth-1:0] value_i);
logic signed [ElementWidth:0] s_shifted;
begin
s_shifted = value_i + 128;
if (s_shifted < 0) begin
return 8'd0;
end
if (s_shifted > 255) begin
return 8'd255;
end
return s_shifted[7:0];
end
endfunction
assign s_state_q = state_e'(s_state_bits_q);
assign start_ready_o = s_state_q == Idle;
assign block_o = s_block_q;
assign dc_o = s_dc_q;
assign result_valid_o = s_state_q == Result;
assign error_o = s_err_q;
assign s_entropy_start = (s_state_q == Idle) && start_i;
assign s_entropy_ready = s_state_q == Entropy;
assign s_quant_start = (s_state_q == Entropy) && s_entropy_valid;
assign s_quant_ready = s_state_q == Dequantize;
assign s_transform_start = (s_state_q == Dequantize) && s_quant_valid;
assign s_transform_ready = s_state_q == Transform;
always_comb begin
for (int unsigned index = 0; index < 64; index++) begin
s_clamped_block[index*8+:8] =
clamp_sample(s_transform_block[index*ElementWidth+:ElementWidth]);
end
end
always_comb begin
s_state_d = s_state_q;
s_dc_d = s_dc_q;
s_block_d = s_block_q;
s_err_d = s_err_q;
unique case (s_state_q)
Idle: begin
if (start_i) begin
s_err_d = 1'b0;
s_state_d = Entropy;
end
end
Entropy: begin
if (s_entropy_valid) begin
s_dc_d = s_entropy_dc;
s_err_d |= s_entropy_err;
s_state_d = Dequantize;
end
end
Dequantize: begin
if (s_quant_valid) begin
s_err_d |= s_quant_err || s_quant_overflow;
s_state_d = Transform;
end
end
Transform: begin
if (s_transform_valid) begin
s_block_d = s_clamped_block;
s_state_d = Result;
end
end
Result: begin
if (result_ready_i) begin
s_state_d = Idle;
end
end
default: s_state_d = Idle;
endcase
end
jpeg_entropy_decoder #(
.ElementWidth(ElementWidth),
.WindowWidth (WindowWidth)
) u_entropy_decoder (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_entropy_start),
.previous_dc_i (previous_dc_i),
.dc_code_i (dc_code_i),
.dc_length_i (dc_length_i),
.ac_code_i (ac_code_i),
.ac_length_i (ac_length_i),
.bit_window_i (bit_window_i),
.bit_count_i (bit_count_i),
.bit_consume_o (bit_consume_o),
.bit_consume_valid_o(bit_consume_valid_o),
.start_ready_o (),
.block_o (s_entropy_block),
.dc_o (s_entropy_dc),
.result_valid_o (s_entropy_valid),
.result_ready_i (s_entropy_ready),
.error_o (s_entropy_err)
);
jpeg_quantizer #(
.ElementWidth(ElementWidth)
) u_dequantizer (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_quant_start),
.dequantize_i (1'b1),
.block_i (s_entropy_block),
.quant_i (quant_i),
.reciprocal_i (reciprocal_i),
.start_ready_o (),
.block_o (s_quant_block),
.result_valid_o(s_quant_valid),
.result_ready_i(s_quant_ready),
.table_err_o (s_quant_err),
.overflow_o (s_quant_overflow)
);
jpeg_transform #(
.ElementWidth(ElementWidth)
) u_inverse_transform (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_transform_start),
.inverse_i (1'b1),
.block_i (s_quant_block),
.start_ready_o (),
.block_o (s_transform_block),
.result_valid_o(s_transform_valid),
.result_ready_i(s_transform_ready)
);
dffr #(
.DATA_WIDTH(3)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(ElementWidth)
) u_dc_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dc_d),
.dat_o (s_dc_q)
);
dffr #(
.DATA_WIDTH(64 * 8)
) u_block_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_block_d),
.dat_o (s_block_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
endmodule
+203
View File
@@ -0,0 +1,203 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_block_encoder #(
parameter int unsigned ElementWidth = 24,
parameter int unsigned TokenSlots = 4,
parameter int unsigned TokenWidth = 32
) (
// verilog_format: off -- preserve command, table, token, and block columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic [64*8-1:0] block_i,
input logic signed [ElementWidth-1:0] previous_dc_i,
input logic [64*8-1:0] quant_i,
input logic [64*25-1:0] reciprocal_i,
input logic [12*16-1:0] dc_code_i,
input logic [12*5-1:0] dc_length_i,
input logic [256*16-1:0] ac_code_i,
input logic [256*5-1:0] ac_length_i,
output logic start_ready_o,
output logic [TokenSlots*TokenWidth-1:0] token_bits_o,
output logic [TokenSlots*6-1:0] token_length_o,
output logic [$clog2(TokenSlots+1)-1:0] token_count_o,
output logic token_valid_o,
output logic token_last_o,
input logic token_ready_i,
output logic signed [ElementWidth-1:0] dc_o,
output logic result_valid_o,
input logic result_ready_i,
output logic error_o
// verilog_format: on
);
typedef enum logic [2:0] {
Idle,
Transform,
Quantize,
Entropy,
Result
} state_e;
state_e s_state_d;
state_e s_state_q;
logic [ 2:0] s_state_bits_q;
logic signed [ ElementWidth-1:0] s_dc_d;
logic signed [ ElementWidth-1:0] s_dc_q;
logic s_err_d;
logic s_err_q;
logic [64*ElementWidth-1:0] s_level_shifted;
logic s_transform_start;
logic s_transform_ready;
logic [64*ElementWidth-1:0] s_transform_block;
logic s_transform_valid;
logic s_quant_start;
logic s_quant_ready;
logic [64*ElementWidth-1:0] s_quant_block;
logic s_quant_valid;
logic s_quant_err;
logic s_quant_overflow;
logic s_entropy_start;
logic signed [ ElementWidth-1:0] s_entropy_dc;
logic s_entropy_err;
logic s_entropy_last_accept;
always_comb begin
for (int unsigned index = 0; index < 64; index++) begin
s_level_shifted[index*ElementWidth+:ElementWidth] =
$signed(ElementWidth'({1'b0, block_i[index*8+:8]})) - ElementWidth'(128);
end
end
assign s_state_q = state_e'(s_state_bits_q);
assign start_ready_o = s_state_q == Idle;
assign result_valid_o = s_state_q == Result;
assign dc_o = s_dc_q;
assign error_o = s_err_q;
assign s_transform_start = (s_state_q == Idle) && start_i;
assign s_transform_ready = s_state_q == Transform;
assign s_quant_start = (s_state_q == Transform) && s_transform_valid;
assign s_quant_ready = s_state_q == Quantize;
assign s_entropy_start = (s_state_q == Quantize) && s_quant_valid;
assign s_entropy_last_accept = (s_state_q == Entropy) && token_valid_o && token_ready_i &&
token_last_o;
always_comb begin
s_state_d = s_state_q;
s_dc_d = s_dc_q;
s_err_d = s_err_q;
unique case (s_state_q)
Idle: begin
if (start_i) begin
s_err_d = 1'b0;
s_state_d = Transform;
end
end
Transform: begin
if (s_transform_valid) begin
s_state_d = Quantize;
end
end
Quantize: begin
if (s_quant_valid) begin
s_err_d |= s_quant_err || s_quant_overflow;
s_state_d = Entropy;
end
end
Entropy: begin
if (s_entropy_last_accept) begin
s_dc_d = s_entropy_dc;
s_err_d |= s_entropy_err;
s_state_d = Result;
end
end
Result: begin
if (result_ready_i) begin
s_state_d = Idle;
end
end
default: s_state_d = Idle;
endcase
end
jpeg_transform #(
.ElementWidth(ElementWidth)
) u_forward_transform (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_transform_start),
.inverse_i (1'b0),
.block_i (s_level_shifted),
.start_ready_o (),
.block_o (s_transform_block),
.result_valid_o(s_transform_valid),
.result_ready_i(s_transform_ready)
);
jpeg_quantizer #(
.ElementWidth(ElementWidth)
) u_quantizer (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_quant_start),
.dequantize_i (1'b0),
.block_i (s_transform_block),
.quant_i (quant_i),
.reciprocal_i (reciprocal_i),
.start_ready_o (),
.block_o (s_quant_block),
.result_valid_o(s_quant_valid),
.result_ready_i(s_quant_ready),
.table_err_o (s_quant_err),
.overflow_o (s_quant_overflow)
);
jpeg_entropy_encoder #(
.ElementWidth(ElementWidth),
.TokenSlots (TokenSlots),
.TokenWidth (TokenWidth)
) u_entropy_encoder (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_entropy_start),
.block_i (s_quant_block),
.previous_dc_i (previous_dc_i),
.dc_code_i (dc_code_i),
.dc_length_i (dc_length_i),
.ac_code_i (ac_code_i),
.ac_length_i (ac_length_i),
.start_ready_o (),
.dc_o (s_entropy_dc),
.token_bits_o (token_bits_o),
.token_length_o(token_length_o),
.token_count_o (token_count_o),
.token_valid_o (token_valid_o),
.token_last_o (token_last_o),
.token_ready_i (token_ready_i),
.error_o (s_entropy_err)
);
dffr #(
.DATA_WIDTH(3)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(ElementWidth)
) u_dc_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dc_d),
.dat_o (s_dc_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
endmodule
+147
View File
@@ -0,0 +1,147 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_byte_joiner #(
parameter int unsigned DataWidth = 64
) (
// verilog_format: off -- preserve input byte beat and AXI output columns
input logic clk_i,
input logic rst_n_i,
input logic [DataWidth-1:0] input_data_i,
input logic [DataWidth/8-1:0] input_keep_i,
input logic input_valid_i,
output logic input_ready_o,
input logic input_last_i,
axi4_stream_if.source output_axis,
output logic error_o
// verilog_format: on
);
localparam int unsigned BeatBytes = DataWidth / 8;
localparam int unsigned BufferBytes = BeatBytes * 2;
localparam int unsigned CountWidth = $clog2(BufferBytes + 1);
localparam logic [CountWidth-1:0] BeatBytesCount = CountWidth'(BeatBytes);
localparam logic [CountWidth-1:0] BufferBytesCount = CountWidth'(BufferBytes);
logic [BufferBytes*8-1:0] s_buffer_d;
logic [BufferBytes*8-1:0] s_buffer_q;
logic [ CountWidth-1:0] s_byte_cnt_d;
logic [ CountWidth-1:0] s_byte_cnt_q;
logic s_last_pending_d;
logic s_last_pending_q;
logic s_err_d;
logic s_err_q;
logic [ CountWidth-1:0] s_input_byte_cnt;
logic s_keep_legal;
logic [BufferBytes*8-1:0] s_append_buffer;
logic [ CountWidth-1:0] s_append_cnt;
function automatic logic [BeatBytes-1:0] keep_mask(input logic [CountWidth-1:0] count_i);
logic [BeatBytes:0] s_mask;
begin
s_mask = ((BeatBytes + 1)'(1) << count_i) - 1'b1;
return s_mask[BeatBytes-1:0];
end
endfunction
always_comb begin
s_input_byte_cnt = '0;
s_keep_legal = input_keep_i != '0;
for (int unsigned lane = 0; lane < BeatBytes; lane++) begin
if (input_keep_i[lane]) begin
s_input_byte_cnt += 1'b1;
end else if ((lane < (BeatBytes - 1)) && input_keep_i[lane+1]) begin
s_keep_legal = 1'b0;
end
end
end
assign input_ready_o = !s_last_pending_q && s_keep_legal &&
(s_input_byte_cnt <= BufferBytesCount - s_byte_cnt_q);
assign output_axis.tdata = s_buffer_q[0+:DataWidth];
assign output_axis.tkeep = (s_byte_cnt_q >= BeatBytesCount) ? '1 : keep_mask(s_byte_cnt_q);
assign output_axis.tstrb = output_axis.tkeep;
assign output_axis.tlast = s_last_pending_q && (s_byte_cnt_q <= BeatBytesCount);
assign output_axis.tid = '0;
assign output_axis.tdest = '0;
assign output_axis.tuser = '0;
assign output_axis.tvalid = (s_byte_cnt_q >= BeatBytesCount) ||
(s_last_pending_q && (s_byte_cnt_q != '0));
assign error_o = s_err_q;
always_comb begin
s_buffer_d = s_buffer_q;
s_byte_cnt_d = s_byte_cnt_q;
s_last_pending_d = s_last_pending_q;
s_err_d = s_err_q;
s_append_buffer = s_buffer_q;
s_append_cnt = s_byte_cnt_q;
if (output_axis.tvalid && output_axis.tready) begin
if (s_byte_cnt_q > BeatBytesCount) begin
s_buffer_d = s_buffer_q >> DataWidth;
s_byte_cnt_d = s_byte_cnt_q - BeatBytesCount;
end else begin
s_buffer_d = '0;
s_byte_cnt_d = '0;
s_last_pending_d = 1'b0;
end
end
if (input_valid_i) begin
if (!input_ready_o) begin
s_err_d = 1'b1;
end else begin
s_append_buffer = s_buffer_d;
s_append_cnt = s_byte_cnt_d;
for (int unsigned lane = 0; lane < BeatBytes; lane++) begin
if (input_keep_i[lane]) begin
s_append_buffer[s_append_cnt*8+:8] = input_data_i[lane*8+:8];
s_append_cnt += 1'b1;
end
end
s_buffer_d = s_append_buffer;
s_byte_cnt_d = s_append_cnt;
if (input_last_i) begin
s_last_pending_d = 1'b1;
end
end
end
end
dffr #(
.DATA_WIDTH(BufferBytes * 8)
) u_buffer_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_buffer_d),
.dat_o (s_buffer_q)
);
dffr #(
.DATA_WIDTH(CountWidth)
) u_byte_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_byte_cnt_d),
.dat_o (s_byte_cnt_q)
);
dffr u_last_pending_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_last_pending_d),
.dat_o (s_last_pending_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
`ifndef SYNTHESIS
initial begin
if (DataWidth != 64) begin
$fatal(1, "jpeg_byte_joiner: data width must be 64 bits");
end
end
`endif
endmodule
+137
View File
@@ -0,0 +1,137 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_byte_unpacker #(
parameter int unsigned DataWidth = 64
) (
// verilog_format: off -- preserve AXI stream and byte interface columns
input logic clk_i,
input logic rst_n_i,
axi4_stream_if.sink input_axis,
output logic [7:0] byte_o,
output logic byte_valid_o,
input logic byte_ready_i,
output logic byte_last_o,
output logic error_o
// verilog_format: on
);
localparam int unsigned ByteCount = DataWidth / 8;
localparam int unsigned IndexWidth = $clog2(ByteCount);
logic [ DataWidth-1:0] s_data_d;
logic [ DataWidth-1:0] s_data_q;
logic [ ByteCount-1:0] s_keep_d;
logic [ ByteCount-1:0] s_keep_q;
logic [IndexWidth-1:0] s_index_d;
logic [IndexWidth-1:0] s_index_q;
logic s_last_d;
logic s_last_q;
logic s_valid_d;
logic s_valid_q;
logic s_err_d;
logic s_err_q;
logic [IndexWidth-1:0] s_last_index;
logic s_keep_legal;
always_comb begin
s_last_index = '0;
s_keep_legal = input_axis.tkeep != '0;
for (int unsigned index = 0; index < ByteCount; index++) begin
if (s_keep_q[index]) begin
s_last_index = IndexWidth'(index);
end
if (!input_axis.tkeep[index] && index < (ByteCount - 1) && input_axis.tkeep[index+1]) begin
s_keep_legal = 1'b0;
end
end
if (input_axis.tkeep != input_axis.tstrb) begin
s_keep_legal = 1'b0;
end
end
assign input_axis.tready = !s_valid_q ||
(byte_valid_o && byte_ready_i && (s_index_q == s_last_index));
assign byte_o = s_data_q[s_index_q*8+:8];
assign byte_valid_o = s_valid_q;
assign byte_last_o = s_last_q && (s_index_q == s_last_index);
assign error_o = s_err_q;
always_comb begin
s_data_d = s_data_q;
s_keep_d = s_keep_q;
s_index_d = s_index_q;
s_last_d = s_last_q;
s_valid_d = s_valid_q;
s_err_d = s_err_q;
if (byte_valid_o && byte_ready_i) begin
if (s_index_q == s_last_index) begin
s_valid_d = 1'b0;
end else begin
s_index_d = s_index_q + 1'b1;
end
end
if (input_axis.tvalid && input_axis.tready) begin
s_data_d = input_axis.tdata;
s_keep_d = input_axis.tkeep;
s_index_d = '0;
s_last_d = input_axis.tlast;
s_valid_d = s_keep_legal;
if (!s_keep_legal) begin
s_err_d = 1'b1;
end
end
end
dffr #(
.DATA_WIDTH(DataWidth)
) u_data_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_data_d),
.dat_o (s_data_q)
);
dffr #(
.DATA_WIDTH(ByteCount)
) u_keep_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_keep_d),
.dat_o (s_keep_q)
);
dffr #(
.DATA_WIDTH(IndexWidth)
) u_index_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_index_d),
.dat_o (s_index_q)
);
dffr u_last_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_last_d),
.dat_o (s_last_q)
);
dffr u_valid_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_valid_d),
.dat_o (s_valid_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
`ifndef SYNTHESIS
initial begin
if ((DataWidth < 8) || ((DataWidth % 8) != 0) || ((DataWidth & (DataWidth - 1)) != 0)) begin
$fatal(1, "jpeg_byte_unpacker: data width must be a power-of-two byte multiple");
end
end
`endif
endmodule
+760
View File
@@ -0,0 +1,760 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_decode_core (
// verilog_format: off -- preserve command, JPEG input, pixel output, and result columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic [ 2:0] output_format_i,
axi4_stream_if.sink bitstream_axis,
axi4_stream_if.source pixel_axis,
output logic start_ready_o,
output logic busy_o,
output logic done_o,
output logic [15:0] width_o,
output logic [15:0] height_o,
output logic [ 1:0] sampling_o,
output logic error_o,
output logic [ 4:0] error_code_o
// verilog_format: on
);
typedef enum logic [3:0] {
Idle,
Parse,
CacheLumaStart,
CacheLumaWait,
CacheChromaStart,
CacheChromaWait,
McuStart,
BlockStart,
BlockWait,
McuOutput,
RestartWait,
EoiWait,
Done,
Error
} state_e;
state_e s_state_d;
state_e s_state_q;
logic [ 3:0] s_state_bits_q;
logic [ 2:0] s_output_format_d;
logic [ 2:0] s_output_format_q;
logic [ 11:0] s_mcu_col_d;
logic [ 11:0] s_mcu_col_q;
logic [ 11:0] s_mcu_row_d;
logic [ 11:0] s_mcu_row_q;
logic [ 2:0] s_block_index_d;
logic [ 2:0] s_block_index_q;
logic [ 15:0] s_restart_cnt_d;
logic [ 15:0] s_restart_cnt_q;
logic [ 2:0] s_restart_index_d;
logic [ 2:0] s_restart_index_q;
logic [ 2:0][23:0] s_dc_predictor_d;
logic [ 2:0][23:0] s_dc_predictor_q;
logic s_err_d;
logic s_err_q;
logic [ 4:0] s_err_code_d;
logic [ 4:0] s_err_code_q;
logic [ 7:0] s_input_byte;
logic s_input_byte_valid;
logic s_input_byte_ready;
logic s_input_byte_last;
logic s_unpack_err;
logic s_parser_start;
logic s_parser_byte_ready;
logic [ 1:0] s_parser_table_context;
logic [ 3:0] s_parser_table_kind;
logic [ 7:0] s_parser_table_index;
logic [ 31:0] s_parser_table_data;
logic s_parser_table_write;
logic s_parser_table_commit;
logic s_parser_header_valid;
logic s_parser_header_ready;
logic s_parser_entropy;
logic [ 15:0] s_width;
logic [ 15:0] s_height;
logic [ 1:0] s_sampling;
logic [ 1:0] s_component_cnt;
logic [ 23:0] s_component_id;
logic [ 23:0] s_component_factor;
logic [ 5:0] s_quant_table;
logic [ 5:0] s_dc_table;
logic [ 5:0] s_ac_table;
logic [ 15:0] s_restart_interval;
logic s_parser_err;
logic [ 4:0] s_parser_err_code;
logic [ 31:0] s_table_portal_data;
logic [ 31:0] s_table_portal_status;
logic s_table_lookup;
logic [ 1:0] s_table_lookup_context;
logic [ 3:0] s_table_lookup_kind;
logic [ 7:0] s_table_lookup_index;
logic [ 31:0] s_table_lookup_data;
logic s_table_lookup_valid;
logic s_table_lookup_err;
logic s_cache_start;
logic [ 1:0] s_cache_quant_id;
logic [ 1:0] s_cache_dc_id;
logic [ 1:0] s_cache_ac_id;
logic [ 511:0] s_cache_quant;
logic [1599:0] s_cache_reciprocal;
logic [ 191:0] s_cache_dc_code;
logic [ 59:0] s_cache_dc_len;
logic [4095:0] s_cache_ac_code;
logic [1279:0] s_cache_ac_len;
logic s_cache_valid;
logic s_cache_ready;
logic s_cache_err;
logic [ 511:0] s_luma_quant_d;
logic [ 511:0] s_luma_quant_q;
logic [1599:0] s_luma_reciprocal_d;
logic [1599:0] s_luma_reciprocal_q;
logic [ 191:0] s_luma_dc_code_d;
logic [ 191:0] s_luma_dc_code_q;
logic [ 59:0] s_luma_dc_len_d;
logic [ 59:0] s_luma_dc_len_q;
logic [4095:0] s_luma_ac_code_d;
logic [4095:0] s_luma_ac_code_q;
logic [1279:0] s_luma_ac_len_d;
logic [1279:0] s_luma_ac_len_q;
logic [ 511:0] s_chroma_quant_d;
logic [ 511:0] s_chroma_quant_q;
logic [1599:0] s_chroma_reciprocal_d;
logic [1599:0] s_chroma_reciprocal_q;
logic [ 191:0] s_chroma_dc_code_d;
logic [ 191:0] s_chroma_dc_code_q;
logic [ 59:0] s_chroma_dc_len_d;
logic [ 59:0] s_chroma_dc_len_q;
logic [4095:0] s_chroma_ac_code_d;
logic [4095:0] s_chroma_ac_code_q;
logic [1279:0] s_chroma_ac_len_d;
logic [1279:0] s_chroma_ac_len_q;
logic [ 31:0] s_bit_window;
logic [ 5:0] s_bit_count;
logic [ 5:0] s_bit_consume;
logic s_bit_consume_valid;
logic s_bit_align;
logic s_bit_byte_ready;
logic s_marker_valid;
logic [ 7:0] s_marker;
logic s_marker_ready;
logic s_bit_err;
logic [ 4:0] s_mcu_width;
logic [ 4:0] s_mcu_height;
logic [ 11:0] s_mcu_columns;
logic [ 11:0] s_mcu_rows;
logic [ 15:0] s_mcu_x_base;
logic [ 15:0] s_mcu_y_base;
logic [ 4:0] s_valid_width;
logic [ 4:0] s_valid_height;
logic [ 2:0] s_expected_blocks;
logic [ 2:0] s_block_component;
logic s_last_mcu;
logic [ 511:0] s_selected_quant;
logic [1599:0] s_selected_reciprocal;
logic [ 191:0] s_selected_dc_code;
logic [ 59:0] s_selected_dc_len;
logic [4095:0] s_selected_ac_code;
logic [1279:0] s_selected_ac_len;
logic s_block_start;
logic [ 511:0] s_decoded_block;
logic signed [ 23:0] s_decoded_dc;
logic s_block_valid;
logic s_block_ready;
logic s_block_err;
logic s_reconstructor_start;
logic s_reconstructor_block_ready;
logic s_reconstructor_done;
logic s_reconstructor_err;
always_comb begin
unique case (s_sampling)
2'd0, 2'd1: begin
s_mcu_width = 5'd8;
s_mcu_height = 5'd8;
end
2'd2: begin
s_mcu_width = 5'd16;
s_mcu_height = 5'd8;
end
default: begin
s_mcu_width = 5'd16;
s_mcu_height = 5'd16;
end
endcase
s_mcu_columns = 12'((s_width + 16'(s_mcu_width) - 1'b1) / 16'(s_mcu_width));
s_mcu_rows = 12'((s_height + 16'(s_mcu_height) - 1'b1) / 16'(s_mcu_height));
s_mcu_x_base = s_mcu_col_q * s_mcu_width;
s_mcu_y_base = s_mcu_row_q * s_mcu_height;
s_valid_width = ((s_width - s_mcu_x_base) >= 16'(s_mcu_width)) ?
s_mcu_width : 5'(s_width - s_mcu_x_base);
s_valid_height = ((s_height - s_mcu_y_base) >= 16'(s_mcu_height)) ?
s_mcu_height : 5'(s_height - s_mcu_y_base);
unique case (s_sampling)
2'd0: s_expected_blocks = 3'd1;
2'd1: s_expected_blocks = 3'd3;
2'd2: s_expected_blocks = 3'd4;
default: s_expected_blocks = 3'd6;
endcase
if (s_sampling == 2'd0) begin
s_block_component = 3'd0;
end else if ((s_sampling == 2'd1) && (s_block_index_q != 3'd0)) begin
s_block_component = s_block_index_q;
end else if ((s_sampling == 2'd2) && (s_block_index_q >= 3'd2)) begin
s_block_component = s_block_index_q - 1'b1;
end else if ((s_sampling == 2'd3) && (s_block_index_q >= 3'd4)) begin
s_block_component = s_block_index_q - 3'd3;
end else begin
s_block_component = 3'd0;
end
s_last_mcu = (s_mcu_col_q + 1'b1 == s_mcu_columns) && (s_mcu_row_q + 1'b1 == s_mcu_rows);
end
assign s_selected_quant = (s_block_component == 3'd0) ? s_luma_quant_q : s_chroma_quant_q;
assign s_selected_reciprocal = (s_block_component == 3'd0) ?
s_luma_reciprocal_q : s_chroma_reciprocal_q;
assign s_selected_dc_code = (s_block_component == 3'd0) ? s_luma_dc_code_q : s_chroma_dc_code_q;
assign s_selected_dc_len = (s_block_component == 3'd0) ? s_luma_dc_len_q : s_chroma_dc_len_q;
assign s_selected_ac_code = (s_block_component == 3'd0) ? s_luma_ac_code_q : s_chroma_ac_code_q;
assign s_selected_ac_len = (s_block_component == 3'd0) ? s_luma_ac_len_q : s_chroma_ac_len_q;
assign s_state_q = state_e'(s_state_bits_q);
assign start_ready_o = s_state_q == Idle;
assign busy_o = (s_state_q != Idle) && (s_state_q != Done) && (s_state_q != Error);
assign done_o = s_state_q == Done;
assign width_o = s_width;
assign height_o = s_height;
assign sampling_o = s_sampling;
assign error_o = s_err_q;
assign error_code_o = s_err_code_q;
assign s_parser_start = (s_state_q == Idle) && start_i;
assign s_input_byte_ready = s_parser_entropy ? s_bit_byte_ready : s_parser_byte_ready;
assign s_parser_header_ready = ((s_state_q == CacheLumaWait) && s_cache_valid &&
(s_component_cnt == 2'd1)) ||
((s_state_q == CacheChromaWait) && s_cache_valid);
assign s_cache_start = (s_state_q == CacheLumaStart) || (s_state_q == CacheChromaStart);
assign s_cache_quant_id = (s_state_q == CacheLumaStart) ? s_quant_table[1:0] : s_quant_table[3:2];
assign s_cache_dc_id = (s_state_q == CacheLumaStart) ? s_dc_table[1:0] : s_dc_table[3:2];
assign s_cache_ac_id = (s_state_q == CacheLumaStart) ? s_ac_table[1:0] : s_ac_table[3:2];
assign s_cache_ready = (s_state_q == CacheLumaWait) || (s_state_q == CacheChromaWait);
assign s_bit_align = (s_state_q == RestartWait) || (s_state_q == EoiWait);
assign s_marker_ready = ((s_state_q == RestartWait) && s_marker_valid &&
(s_marker == (8'hd0 + {5'd0, s_restart_index_q}))) ||
((s_state_q == EoiWait) && s_marker_valid && (s_marker == 8'hd9));
assign s_reconstructor_start = s_state_q == McuStart;
assign s_block_start = s_state_q == BlockStart;
assign s_block_ready = (s_state_q == BlockWait) && s_reconstructor_block_ready;
always_comb begin
s_state_d = s_state_q;
s_output_format_d = s_output_format_q;
s_mcu_col_d = s_mcu_col_q;
s_mcu_row_d = s_mcu_row_q;
s_block_index_d = s_block_index_q;
s_restart_cnt_d = s_restart_cnt_q;
s_restart_index_d = s_restart_index_q;
s_dc_predictor_d = s_dc_predictor_q;
s_err_d = s_err_q;
s_err_code_d = s_err_code_q;
s_luma_quant_d = s_luma_quant_q;
s_luma_reciprocal_d = s_luma_reciprocal_q;
s_luma_dc_code_d = s_luma_dc_code_q;
s_luma_dc_len_d = s_luma_dc_len_q;
s_luma_ac_code_d = s_luma_ac_code_q;
s_luma_ac_len_d = s_luma_ac_len_q;
s_chroma_quant_d = s_chroma_quant_q;
s_chroma_reciprocal_d = s_chroma_reciprocal_q;
s_chroma_dc_code_d = s_chroma_dc_code_q;
s_chroma_dc_len_d = s_chroma_dc_len_q;
s_chroma_ac_code_d = s_chroma_ac_code_q;
s_chroma_ac_len_d = s_chroma_ac_len_q;
unique case (s_state_q)
Idle: begin
if (start_i) begin
s_output_format_d = output_format_i;
s_mcu_col_d = 12'd0;
s_mcu_row_d = 12'd0;
s_block_index_d = 3'd0;
s_restart_cnt_d = 16'd0;
s_restart_index_d = 3'd0;
s_dc_predictor_d = '0;
s_err_d = output_format_i > 3'd4;
s_err_code_d = 5'd0;
s_state_d = Parse;
end
end
Parse: begin
if (s_parser_err || s_unpack_err) begin
s_err_d = 1'b1;
s_err_code_d = s_parser_err ? s_parser_err_code : 5'd10;
s_state_d = Error;
end else if (s_parser_header_valid) begin
if ((s_width == 16'd0) || (s_height == 16'd0) || (s_width > 16'd2048) ||
(s_height > 16'd2048) ||
((s_component_cnt == 2'd3) &&
((s_quant_table[5:4] != s_quant_table[3:2]) ||
(s_dc_table[5:4] != s_dc_table[3:2]) ||
(s_ac_table[5:4] != s_ac_table[3:2])))) begin
s_err_d = 1'b1;
s_err_code_d = 5'd7;
s_state_d = Error;
end else begin
s_state_d = CacheLumaStart;
end
end
end
CacheLumaStart: s_state_d = CacheLumaWait;
CacheLumaWait: begin
if (s_cache_valid) begin
s_luma_quant_d = s_cache_quant;
s_luma_reciprocal_d = s_cache_reciprocal;
s_luma_dc_code_d = s_cache_dc_code;
s_luma_dc_len_d = s_cache_dc_len;
s_luma_ac_code_d = s_cache_ac_code;
s_luma_ac_len_d = s_cache_ac_len;
if (s_cache_err) begin
s_err_d = 1'b1;
s_err_code_d = 5'd3;
s_state_d = Error;
end else if (s_component_cnt == 2'd1) begin
s_state_d = McuStart;
end else begin
s_state_d = CacheChromaStart;
end
end
end
CacheChromaStart: s_state_d = CacheChromaWait;
CacheChromaWait: begin
if (s_cache_valid) begin
s_chroma_quant_d = s_cache_quant;
s_chroma_reciprocal_d = s_cache_reciprocal;
s_chroma_dc_code_d = s_cache_dc_code;
s_chroma_dc_len_d = s_cache_dc_len;
s_chroma_ac_code_d = s_cache_ac_code;
s_chroma_ac_len_d = s_cache_ac_len;
if (s_cache_err) begin
s_err_d = 1'b1;
s_err_code_d = 5'd3;
s_state_d = Error;
end else begin
s_state_d = McuStart;
end
end
end
McuStart: begin
s_block_index_d = 3'd0;
s_state_d = BlockStart;
end
BlockStart: begin
if (s_block_start) begin
s_state_d = BlockWait;
end
end
BlockWait: begin
if (s_block_valid && s_reconstructor_block_ready) begin
s_dc_predictor_d[s_block_component] = s_decoded_dc;
if (s_block_err) begin
s_err_d = 1'b1;
s_err_code_d = 5'd9;
s_state_d = Error;
end else if (s_block_index_q + 1'b1 == s_expected_blocks) begin
s_state_d = McuOutput;
end else begin
s_block_index_d = s_block_index_q + 1'b1;
s_state_d = BlockStart;
end
end
end
McuOutput: begin
if (s_reconstructor_done) begin
if (s_reconstructor_err) begin
s_err_d = 1'b1;
s_err_code_d = 5'd18;
s_state_d = Error;
end else if (s_last_mcu) begin
s_state_d = EoiWait;
end else if ((s_restart_interval != 16'd0) &&
(s_restart_cnt_q + 1'b1 >= s_restart_interval)) begin
s_state_d = RestartWait;
end else begin
s_restart_cnt_d = s_restart_cnt_q + 1'b1;
if (s_mcu_col_q + 1'b1 == s_mcu_columns) begin
s_mcu_col_d = 12'd0;
s_mcu_row_d = s_mcu_row_q + 1'b1;
end else begin
s_mcu_col_d = s_mcu_col_q + 1'b1;
end
s_state_d = McuStart;
end
end
end
RestartWait: begin
if (s_marker_valid) begin
if (s_marker != (8'hd0 + {5'd0, s_restart_index_q})) begin
s_err_d = 1'b1;
s_err_code_d = 5'd11;
s_state_d = Error;
end else begin
s_dc_predictor_d = '0;
s_restart_cnt_d = 16'd0;
s_restart_index_d = s_restart_index_q + 1'b1;
if (s_mcu_col_q + 1'b1 == s_mcu_columns) begin
s_mcu_col_d = 12'd0;
s_mcu_row_d = s_mcu_row_q + 1'b1;
end else begin
s_mcu_col_d = s_mcu_col_q + 1'b1;
end
s_state_d = McuStart;
end
end
end
EoiWait: begin
if (s_marker_valid) begin
if (s_marker != 8'hd9) begin
s_err_d = 1'b1;
s_err_code_d = 5'd4;
s_state_d = Error;
end else begin
s_err_d |= s_bit_err;
s_state_d = Done;
end
end
end
Done: s_state_d = Idle;
Error: s_state_d = Error;
default: s_state_d = Error;
endcase
end
jpeg_byte_unpacker u_byte_unpacker (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.input_axis (bitstream_axis),
.byte_o (s_input_byte),
.byte_valid_o(s_input_byte_valid),
.byte_ready_i(s_input_byte_ready),
.byte_last_o (s_input_byte_last),
.error_o (s_unpack_err)
);
jpeg_marker_parser u_marker_parser (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_parser_start),
.table_context_i (2'd0),
.byte_i (s_input_byte),
.byte_valid_i (s_input_byte_valid && !s_parser_entropy),
.byte_ready_o (s_parser_byte_ready),
.table_context_o (s_parser_table_context),
.table_kind_o (s_parser_table_kind),
.table_index_o (s_parser_table_index),
.table_data_o (s_parser_table_data),
.table_write_o (s_parser_table_write),
.table_commit_o (s_parser_table_commit),
.header_valid_o (s_parser_header_valid),
.header_ready_i (s_parser_header_ready),
.entropy_o (s_parser_entropy),
.width_o (s_width),
.height_o (s_height),
.sampling_o (s_sampling),
.component_count_o (s_component_cnt),
.component_id_o (s_component_id),
.component_factor_o(s_component_factor),
.quant_table_o (s_quant_table),
.dc_table_o (s_dc_table),
.ac_table_o (s_ac_table),
.restart_interval_o(s_restart_interval),
.marker_count_o (),
.error_o (s_parser_err),
.error_code_o (s_parser_err_code)
);
jpeg_table_store u_table_store (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.portal_context_i (s_parser_table_context),
.portal_kind_i (s_parser_table_kind),
.portal_index_i (s_parser_table_index),
.portal_write_data_i(s_parser_table_data),
.portal_write_i (s_parser_table_write),
.portal_commit_i (s_parser_table_commit),
.portal_default_i (1'b0),
.portal_clear_i (s_parser_start),
.portal_read_data_o (s_table_portal_data),
.portal_status_o (s_table_portal_status),
.lookup_i (s_table_lookup),
.lookup_context_i (s_table_lookup_context),
.lookup_kind_i (s_table_lookup_kind),
.lookup_index_i (s_table_lookup_index),
.lookup_data_o (s_table_lookup_data),
.lookup_valid_o (s_table_lookup_valid),
.lookup_err_o (s_table_lookup_err)
);
jpeg_table_cache u_table_cache (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_cache_start),
.context_i (2'd0),
.quant_id_i (s_cache_quant_id),
.dc_id_i (s_cache_dc_id),
.ac_id_i (s_cache_ac_id),
.start_ready_o (),
.lookup_o (s_table_lookup),
.lookup_context_o(s_table_lookup_context),
.lookup_kind_o (s_table_lookup_kind),
.lookup_index_o (s_table_lookup_index),
.lookup_data_i (s_table_lookup_data),
.lookup_valid_i (s_table_lookup_valid),
.lookup_err_i (s_table_lookup_err),
.quant_o (s_cache_quant),
.reciprocal_o (s_cache_reciprocal),
.dc_code_o (s_cache_dc_code),
.dc_length_o (s_cache_dc_len),
.ac_code_o (s_cache_ac_code),
.ac_length_o (s_cache_ac_len),
.result_valid_o (s_cache_valid),
.result_ready_i (s_cache_ready),
.error_o (s_cache_err)
);
jpeg_bit_reader u_bit_reader (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.byte_i (s_input_byte),
.byte_valid_i (s_input_byte_valid && s_parser_entropy),
.byte_ready_o (s_bit_byte_ready),
.byte_last_i (s_input_byte_last),
.bit_consume_i (s_bit_consume),
.bit_consume_valid_i(s_bit_consume_valid),
.align_i (s_bit_align),
.bit_window_o (s_bit_window),
.bit_count_o (s_bit_count),
.marker_valid_o (s_marker_valid),
.marker_o (s_marker),
.marker_ready_i (s_marker_ready),
.error_o (s_bit_err)
);
jpeg_block_decoder u_block_decoder (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_block_start),
.previous_dc_i (s_dc_predictor_q[s_block_component]),
.quant_i (s_selected_quant),
.reciprocal_i (s_selected_reciprocal),
.dc_code_i (s_selected_dc_code),
.dc_length_i (s_selected_dc_len),
.ac_code_i (s_selected_ac_code),
.ac_length_i (s_selected_ac_len),
.bit_window_i (s_bit_window),
.bit_count_i (s_bit_count),
.bit_consume_o (s_bit_consume),
.bit_consume_valid_o(s_bit_consume_valid),
.start_ready_o (),
.block_o (s_decoded_block),
.dc_o (s_decoded_dc),
.result_valid_o (s_block_valid),
.result_ready_i (s_block_ready),
.error_o (s_block_err)
);
jpeg_mcu_reconstructor u_mcu_reconstructor (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_reconstructor_start),
.sampling_i (s_sampling),
.format_i (s_output_format_q),
.valid_width_i (s_valid_width),
.valid_height_i(s_valid_height),
.block_i (s_decoded_block),
.block_valid_i (s_block_valid),
.block_ready_o (s_reconstructor_block_ready),
.start_ready_o (),
.pixel_axis (pixel_axis),
.done_o (s_reconstructor_done),
.error_o (s_reconstructor_err)
);
dffr #(
.DATA_WIDTH(4)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(3)
) u_output_format_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_output_format_d),
.dat_o (s_output_format_q)
);
dffr #(
.DATA_WIDTH(12)
) u_mcu_col_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_mcu_col_d),
.dat_o (s_mcu_col_q)
);
dffr #(
.DATA_WIDTH(12)
) u_mcu_row_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_mcu_row_d),
.dat_o (s_mcu_row_q)
);
dffr #(
.DATA_WIDTH(3)
) u_block_index_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_block_index_d),
.dat_o (s_block_index_q)
);
dffr #(
.DATA_WIDTH(16)
) u_restart_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_restart_cnt_d),
.dat_o (s_restart_cnt_q)
);
dffr #(
.DATA_WIDTH(3)
) u_restart_index_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_restart_index_d),
.dat_o (s_restart_index_q)
);
dffr #(
.DATA_WIDTH(3 * 24)
) u_dc_predictor_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dc_predictor_d),
.dat_o (s_dc_predictor_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
dffr #(
.DATA_WIDTH(5)
) u_err_code_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_code_d),
.dat_o (s_err_code_q)
);
dffr #(
.DATA_WIDTH(512)
) u_luma_quant_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_luma_quant_d),
.dat_o (s_luma_quant_q)
);
dffr #(
.DATA_WIDTH(1600)
) u_luma_reciprocal_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_luma_reciprocal_d),
.dat_o (s_luma_reciprocal_q)
);
dffr #(
.DATA_WIDTH(192)
) u_luma_dc_code_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_luma_dc_code_d),
.dat_o (s_luma_dc_code_q)
);
dffr #(
.DATA_WIDTH(60)
) u_luma_dc_len_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_luma_dc_len_d),
.dat_o (s_luma_dc_len_q)
);
dffr #(
.DATA_WIDTH(4096)
) u_luma_ac_code_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_luma_ac_code_d),
.dat_o (s_luma_ac_code_q)
);
dffr #(
.DATA_WIDTH(1280)
) u_luma_ac_len_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_luma_ac_len_d),
.dat_o (s_luma_ac_len_q)
);
dffr #(
.DATA_WIDTH(512)
) u_chroma_quant_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_chroma_quant_d),
.dat_o (s_chroma_quant_q)
);
dffr #(
.DATA_WIDTH(1600)
) u_chroma_reciprocal_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_chroma_reciprocal_d),
.dat_o (s_chroma_reciprocal_q)
);
dffr #(
.DATA_WIDTH(192)
) u_chroma_dc_code_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_chroma_dc_code_d),
.dat_o (s_chroma_dc_code_q)
);
dffr #(
.DATA_WIDTH(60)
) u_chroma_dc_len_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_chroma_dc_len_d),
.dat_o (s_chroma_dc_len_q)
);
dffr #(
.DATA_WIDTH(4096)
) u_chroma_ac_code_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_chroma_ac_code_d),
.dat_o (s_chroma_ac_code_q)
);
dffr #(
.DATA_WIDTH(1280)
) u_chroma_ac_len_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_chroma_ac_len_d),
.dat_o (s_chroma_ac_len_q)
);
endmodule
+115
View File
@@ -0,0 +1,115 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
`ifndef RETROSOC_JPEG_DEFINE_SVH
`define RETROSOC_JPEG_DEFINE_SVH
// verilog_format: off -- keep the handwritten hardware/software ABI column aligned
`define APB4_JPEG__IP_ID 12'h000
`define APB4_JPEG__IP_VERSION 12'h004
`define APB4_JPEG__CAPABILITY0 12'h008
`define APB4_JPEG__CAPABILITY1 12'h00c
`define APB4_JPEG__COMMAND 12'h010
`define APB4_JPEG__STATUS 12'h014
`define APB4_JPEG__IRQ_STATE 12'h018
`define APB4_JPEG__IRQ_ENABLE 12'h01c
`define APB4_JPEG__IRQ_TEST 12'h020
`define APB4_JPEG__ERROR_STATUS 12'h024
`define APB4_JPEG__ERROR_ADDRESS 12'h028
`define APB4_JPEG__ERROR_DETAIL 12'h02c
`define APB4_JPEG__PERF_CONTROL 12'h030
`define APB4_JPEG__CYCLES_LO 12'h034
`define APB4_JPEG__CYCLES_HI 12'h038
`define APB4_JPEG__PIXELS 12'h03c
`define APB4_JPEG__INPUT_BYTES 12'h040
`define APB4_JPEG__OUTPUT_BYTES 12'h044
`define APB4_JPEG__READ_STALL 12'h048
`define APB4_JPEG__WRITE_STALL 12'h04c
`define APB4_JPEG__JOB_CONFIG 12'h080
`define APB4_JPEG__IMAGE_SIZE 12'h084
`define APB4_JPEG__INPUT_FORMAT 12'h088
`define APB4_JPEG__OUTPUT_FORMAT 12'h08c
`define APB4_JPEG__ENCODE_CONFIG 12'h090
`define APB4_JPEG__RESTART_INTERVAL 12'h094
`define APB4_JPEG__BITSTREAM_ADDR 12'h098
`define APB4_JPEG__BITSTREAM_SIZE 12'h09c
`define APB4_JPEG__PLANE0_ADDR 12'h0a0
`define APB4_JPEG__PLANE0_STRIDE 12'h0a4
`define APB4_JPEG__PLANE1_ADDR 12'h0a8
`define APB4_JPEG__PLANE1_STRIDE 12'h0ac
`define APB4_JPEG__PLANE2_ADDR 12'h0b0
`define APB4_JPEG__PLANE2_STRIDE 12'h0b4
`define APB4_JPEG__METADATA_ADDR 12'h0b8
`define APB4_JPEG__METADATA_LENGTH 12'h0bc
`define APB4_JPEG__RESULT_SIZE 12'h0c0
`define APB4_JPEG__RESULT_IMAGE_SIZE 12'h0c4
`define APB4_JPEG__RESULT_FORMAT 12'h0c8
`define APB4_JPEG__RESULT_MARKERS 12'h0cc
`define APB4_JPEG__RING_BASE 12'h100
`define APB4_JPEG__RING_SIZE 12'h104
`define APB4_JPEG__RING_HEAD 12'h108
`define APB4_JPEG__RING_TAIL 12'h10c
`define APB4_JPEG__RING_CONTROL 12'h110
`define APB4_JPEG__RING_STATUS 12'h114
`define APB4_JPEG__IRQ_COALESCE 12'h118
`define APB4_JPEG__DOORBELL 12'h11c
`define APB4_JPEG__TABLE_CONTEXT 12'h200
`define APB4_JPEG__TABLE_KIND 12'h204
`define APB4_JPEG__TABLE_INDEX 12'h208
`define APB4_JPEG__TABLE_DATA 12'h20c
`define APB4_JPEG__TABLE_COMMAND 12'h210
`define APB4_JPEG__TABLE_STATUS 12'h214
`define APB4_JPEG__COMMAND_START 0
`define APB4_JPEG__COMMAND_ABORT 1
`define APB4_JPEG__COMMAND_SOFT_RESET 2
`define APB4_JPEG__COMMAND_RING_KICK 3
`define APB4_JPEG__STATUS_BUSY 0
`define APB4_JPEG__STATUS_RING_ACTIVE 1
`define APB4_JPEG__STATUS_ENCODE 2
`define APB4_JPEG__STATUS_IDLE 3
`define APB4_JPEG__IRQ_JOB_DONE 0
`define APB4_JPEG__IRQ_RING_EVENT 1
`define APB4_JPEG__IRQ_HEADER_READY 2
`define APB4_JPEG__IRQ_ABORT_DONE 3
`define APB4_JPEG__IRQ_ERROR 4
`define APB4_JPEG__JOB_CONFIG_ENCODE 0
`define APB4_JPEG__JOB_CONFIG_AUTO_HEADER 1
`define APB4_JPEG__JOB_CONFIG_STRICT 2
`define APB4_JPEG__JOB_CONFIG_METADATA 3
`define APB4_JPEG__JOB_CONFIG_TABLE 8
`define APB4_JPEG__DESCRIPTOR_OWN 0
`define APB4_JPEG__DESCRIPTOR_IOC 1
`define APB4_JPEG__DESCRIPTOR_ENCODE 2
`define APB4_JPEG__DESCRIPTOR_AUTO_HEADER 3
`define APB4_JPEG__DESCRIPTOR_STRICT 4
`define APB4_JPEG__DESCRIPTOR_METADATA 5
`define APB4_JPEG__DESCRIPTOR_TABLE 8
`define APB4_JPEG__DESCRIPTOR_INPUT_FMT 12
`define APB4_JPEG__DESCRIPTOR_OUTPUT_FMT 16
`define APB4_JPEG__DESCRIPTOR_SAMPLING 20
`define APB4_JPEG__DESCRIPTOR_STATUS_DONE 0
`define APB4_JPEG__DESCRIPTOR_STATUS_ERR 1
`define APB4_JPEG__DESCRIPTOR_STATUS_ABORT 2
`define APB4_JPEG__DESCRIPTOR_STATUS_CODE 8
`define APB4_JPEG__RING_CONTROL_ENABLE 0
`define APB4_JPEG__RING_CONTROL_STOP_ERR 1
`define APB4_JPEG__RING_STATUS_ACTIVE 0
`define APB4_JPEG__RING_STATUS_EMPTY 1
`define APB4_JPEG__RING_STATUS_STALLED 2
`define APB4_JPEG__RING_STATUS_ERROR 3
`define APB4_JPEG__TABLE_COMMAND_COMMIT 0
`define APB4_JPEG__TABLE_COMMAND_DEFAULT 1
`define APB4_JPEG__TABLE_COMMAND_CLEAR 2
// verilog_format: on
`endif
+693
View File
@@ -0,0 +1,693 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
`include "axi4_define.svh"
module jpeg_dma #(
parameter int unsigned AddrWidth = 32,
parameter int unsigned DataWidth = 64,
parameter int unsigned MaxBurstBeats = 16
) (
// verilog_format: off -- preserve read, write, status, and AXI interface columns
input logic clk_i,
input logic rst_n_i,
input logic abort_i,
input logic quiesce_i,
input logic read_start_i,
input logic [AddrWidth-1:0] read_addr_i,
input logic [31:0] read_line_bytes_i,
input logic [31:0] read_stride_i,
input logic [15:0] read_lines_i,
output logic read_busy_o,
output logic read_done_o,
axi4_stream_if.source read_axis,
input logic write_start_i,
input logic [AddrWidth-1:0] write_addr_i,
input logic [31:0] write_line_bytes_i,
input logic [31:0] write_stride_i,
input logic [15:0] write_lines_i,
output logic write_busy_o,
output logic write_done_o,
axi4_stream_if.sink write_axis,
output logic error_o,
output logic error_read_o,
output logic [ 1:0] error_resp_o,
output logic [AddrWidth-1:0] error_addr_o,
output logic [31:0] read_bytes_o,
output logic [31:0] write_bytes_o,
output logic [31:0] read_stall_o,
output logic [31:0] write_stall_o,
axi4_if.master axi4
// verilog_format: on
);
localparam int unsigned BeatBytes = DataWidth / 8;
localparam int unsigned StrobeWidth = DataWidth / 8;
typedef enum logic [1:0] {
ReadIdle,
ReadStart,
ReadData
} read_state_e;
typedef enum logic [1:0] {
WriteIdle,
WriteStart,
WriteData,
WriteResponse
} write_state_e;
read_state_e s_read_state_d;
read_state_e s_read_state_q;
logic [ 1:0] s_read_state_bits_q;
write_state_e s_write_state_d;
write_state_e s_write_state_q;
logic [ 1:0] s_write_state_bits_q;
logic [ AddrWidth-1:0] s_read_line_base_d;
logic [ AddrWidth-1:0] s_read_line_base_q;
logic [ AddrWidth-1:0] s_read_addr_d;
logic [ AddrWidth-1:0] s_read_addr_q;
logic [ 31:0] s_read_line_bytes_d;
logic [ 31:0] s_read_line_bytes_q;
logic [ 31:0] s_read_remaining_d;
logic [ 31:0] s_read_remaining_q;
logic [ 31:0] s_read_stride_d;
logic [ 31:0] s_read_stride_q;
logic [ 15:0] s_read_lines_d;
logic [ 15:0] s_read_lines_q;
logic s_read_first_d;
logic s_read_first_q;
logic s_read_abort_d;
logic s_read_abort_q;
logic s_read_done_d;
logic s_read_done_q;
logic [ AddrWidth-1:0] s_write_line_base_d;
logic [ AddrWidth-1:0] s_write_line_base_q;
logic [ AddrWidth-1:0] s_write_addr_d;
logic [ AddrWidth-1:0] s_write_addr_q;
logic [ 31:0] s_write_line_bytes_d;
logic [ 31:0] s_write_line_bytes_q;
logic [ 31:0] s_write_remaining_d;
logic [ 31:0] s_write_remaining_q;
logic [ 31:0] s_write_stride_d;
logic [ 31:0] s_write_stride_q;
logic [ 15:0] s_write_lines_d;
logic [ 15:0] s_write_lines_q;
logic [ 4:0] s_write_burst_beats_d;
logic [ 4:0] s_write_burst_beats_q;
logic s_write_abort_d;
logic s_write_abort_q;
logic s_write_done_d;
logic s_write_done_q;
logic s_err_d;
logic s_err_q;
logic s_err_read_d;
logic s_err_read_q;
logic [ 1:0] s_err_resp_d;
logic [ 1:0] s_err_resp_q;
logic [ AddrWidth-1:0] s_err_addr_d;
logic [ AddrWidth-1:0] s_err_addr_q;
logic [ 31:0] s_read_bytes_d;
logic [ 31:0] s_read_bytes_q;
logic [ 31:0] s_write_bytes_d;
logic [ 31:0] s_write_bytes_q;
logic [ 31:0] s_read_stall_d;
logic [ 31:0] s_read_stall_q;
logic [ 31:0] s_write_stall_d;
logic [ 31:0] s_write_stall_q;
logic s_read_start_valid;
logic s_read_start_ready;
logic [ 4:0] s_read_beats;
logic s_read_master_busy;
logic s_read_beat_valid;
logic s_read_beat_ready;
logic [ DataWidth-1:0] s_read_data;
logic [ 1:0] s_read_resp;
logic s_read_last;
logic s_read_expected_last;
logic s_read_id_err;
logic s_read_master_done;
logic s_write_start_valid;
logic s_write_start_ready;
logic [ 4:0] s_write_beats;
logic s_write_master_busy;
logic s_write_data_valid;
logic s_write_data_ready;
logic [ DataWidth-1:0] s_write_data;
logic [StrobeWidth-1:0] s_write_strobe;
logic s_write_master_done;
logic [ 1:0] s_write_resp;
logic s_write_id_err;
logic [StrobeWidth-1:0] s_read_keep;
logic [StrobeWidth-1:0] s_write_expected_keep;
logic [ 31:0] s_read_beat_bytes;
logic [ 31:0] s_write_beat_bytes;
logic s_read_fault;
logic s_write_fault;
logic s_write_payload_fault;
function automatic logic [4:0] choose_burst(input logic [11:0] addr_low_i,
input logic [31:0] bytes_i);
logic [31:0] s_beats_needed;
logic [31:0] s_boundary_beats;
logic [31:0] s_selected;
begin
s_beats_needed = (bytes_i + BeatBytes - 1) / BeatBytes;
s_boundary_beats = (32'd4096 - {20'd0, addr_low_i}) / BeatBytes;
s_selected = s_beats_needed;
if (s_selected > MaxBurstBeats) begin
s_selected = MaxBurstBeats;
end
if (s_selected > s_boundary_beats) begin
s_selected = s_boundary_beats;
end
if (s_selected == 32'd0) begin
s_selected = 32'd1;
end
return s_selected[4:0];
end
endfunction
function automatic logic [StrobeWidth-1:0] byte_mask(input logic [31:0] bytes_i);
logic [StrobeWidth:0] s_mask;
begin
if (bytes_i >= BeatBytes) begin
return '1;
end
s_mask = ({{StrobeWidth{1'b0}}, 1'b1} << bytes_i) - 1'b1;
return s_mask[StrobeWidth-1:0];
end
endfunction
function automatic logic [31:0] saturating_increment(input logic [31:0] value_i);
return (&value_i) ? value_i : value_i + 1'b1;
endfunction
assign s_read_state_q = read_state_e'(s_read_state_bits_q);
assign s_write_state_q = write_state_e'(s_write_state_bits_q);
assign s_read_beats = choose_burst(s_read_addr_q[11:0], s_read_remaining_q);
assign s_write_beats = choose_burst(s_write_addr_q[11:0], s_write_remaining_q);
assign s_read_beat_bytes = (s_read_remaining_q >= BeatBytes) ? BeatBytes : s_read_remaining_q;
assign s_write_beat_bytes = (s_write_remaining_q >= BeatBytes) ? BeatBytes : s_write_remaining_q;
assign s_read_keep = byte_mask(s_read_beat_bytes);
assign s_write_expected_keep = byte_mask(s_write_beat_bytes);
assign s_read_start_valid = (s_read_state_q == ReadStart) && !s_read_abort_q;
assign s_read_beat_ready = (s_read_state_q == ReadData) &&
(s_read_abort_q || s_err_q || read_axis.tready);
assign read_axis.tdata = s_read_data;
assign read_axis.tkeep = s_read_keep;
assign read_axis.tstrb = s_read_keep;
assign read_axis.tlast = s_read_remaining_q <= BeatBytes;
assign read_axis.tid = '0;
assign read_axis.tdest = '0;
assign read_axis.tuser = s_read_first_q;
assign read_axis.tvalid = (s_read_state_q == ReadData) && s_read_beat_valid &&
!s_read_abort_q && !s_err_q && !s_read_fault;
assign s_read_fault = s_read_beat_valid &&
((s_read_resp != `AXI4_RESP_OKAY) || s_read_id_err ||
(s_read_last != s_read_expected_last));
assign s_write_start_valid = (s_write_state_q == WriteStart) && !s_write_abort_q;
assign s_write_payload_fault = (write_axis.tkeep != s_write_expected_keep) ||
(write_axis.tstrb != s_write_expected_keep) ||
(write_axis.tlast != (s_write_remaining_q <= BeatBytes));
assign s_write_data_valid = (s_write_state_q == WriteData) &&
(s_write_abort_q || s_err_q || write_axis.tvalid);
assign s_write_data = (s_write_abort_q || s_err_q || s_write_payload_fault) ?
'0 : write_axis.tdata;
assign s_write_strobe = (s_write_abort_q || s_err_q || s_write_payload_fault) ?
'0 : write_axis.tkeep;
assign write_axis.tready = (s_write_state_q == WriteData) && s_write_data_ready &&
!s_write_abort_q && !s_err_q;
assign s_write_fault = s_write_master_done &&
((s_write_resp != `AXI4_RESP_OKAY) || s_write_id_err);
assign read_busy_o = (s_read_state_q != ReadIdle) || s_read_master_busy;
assign write_busy_o = (s_write_state_q != WriteIdle) || s_write_master_busy;
assign read_done_o = s_read_done_q;
assign write_done_o = s_write_done_q;
assign error_o = s_err_q;
assign error_read_o = s_err_read_q;
assign error_resp_o = s_err_resp_q;
assign error_addr_o = s_err_addr_q;
assign read_bytes_o = s_read_bytes_q;
assign write_bytes_o = s_write_bytes_q;
assign read_stall_o = s_read_stall_q;
assign write_stall_o = s_write_stall_q;
always_comb begin
s_read_state_d = s_read_state_q;
s_read_line_base_d = s_read_line_base_q;
s_read_addr_d = s_read_addr_q;
s_read_line_bytes_d = s_read_line_bytes_q;
s_read_remaining_d = s_read_remaining_q;
s_read_stride_d = s_read_stride_q;
s_read_lines_d = s_read_lines_q;
s_read_first_d = s_read_first_q;
s_read_abort_d = s_read_abort_q;
s_read_done_d = 1'b0;
if (abort_i && (s_read_state_q != ReadIdle)) begin
s_read_abort_d = 1'b1;
end
unique case (s_read_state_q)
ReadIdle: begin
s_read_abort_d = 1'b0;
if (read_start_i && !quiesce_i) begin
if ((read_line_bytes_i == 32'd0) || (read_lines_i == 16'd0) || (read_addr_i[$clog2(
BeatBytes
)-1:0] != '0) || (read_stride_i < read_line_bytes_i)) begin
s_read_done_d = 1'b1;
end else begin
s_read_line_base_d = read_addr_i;
s_read_addr_d = read_addr_i;
s_read_line_bytes_d = read_line_bytes_i;
s_read_remaining_d = read_line_bytes_i;
s_read_stride_d = read_stride_i;
s_read_lines_d = read_lines_i;
s_read_first_d = 1'b1;
s_read_state_d = ReadStart;
end
end
end
ReadStart: begin
if (s_read_start_valid && s_read_start_ready) begin
s_read_state_d = ReadData;
end
end
ReadData: begin
if (s_read_beat_valid && s_read_beat_ready) begin
s_read_addr_d = s_read_addr_q + s_read_beat_bytes;
s_read_remaining_d = s_read_remaining_q - s_read_beat_bytes;
s_read_first_d = 1'b0;
if (s_read_last) begin
if (s_read_abort_q || s_read_fault || s_err_q) begin
s_read_done_d = 1'b1;
s_read_state_d = ReadIdle;
end else if (s_read_remaining_q <= BeatBytes) begin
if (s_read_lines_q == 16'd1) begin
s_read_done_d = 1'b1;
s_read_state_d = ReadIdle;
end else begin
s_read_line_base_d = s_read_line_base_q + s_read_stride_q;
s_read_addr_d = s_read_line_base_q + s_read_stride_q;
s_read_remaining_d = s_read_line_bytes_q;
s_read_lines_d = s_read_lines_q - 1'b1;
s_read_state_d = ReadStart;
end
end else begin
s_read_state_d = ReadStart;
end
end
end
end
default: s_read_state_d = ReadIdle;
endcase
end
always_comb begin
s_write_state_d = s_write_state_q;
s_write_line_base_d = s_write_line_base_q;
s_write_addr_d = s_write_addr_q;
s_write_line_bytes_d = s_write_line_bytes_q;
s_write_remaining_d = s_write_remaining_q;
s_write_stride_d = s_write_stride_q;
s_write_lines_d = s_write_lines_q;
s_write_burst_beats_d = s_write_burst_beats_q;
s_write_abort_d = s_write_abort_q;
s_write_done_d = 1'b0;
if (abort_i && (s_write_state_q != WriteIdle)) begin
s_write_abort_d = 1'b1;
end
unique case (s_write_state_q)
WriteIdle: begin
s_write_abort_d = 1'b0;
if (write_start_i && !quiesce_i) begin
if ((write_line_bytes_i == 32'd0) || (write_lines_i == 16'd0) || (write_addr_i[$clog2(
BeatBytes
)-1:0] != '0) || (write_stride_i < write_line_bytes_i)) begin
s_write_done_d = 1'b1;
end else begin
s_write_line_base_d = write_addr_i;
s_write_addr_d = write_addr_i;
s_write_line_bytes_d = write_line_bytes_i;
s_write_remaining_d = write_line_bytes_i;
s_write_stride_d = write_stride_i;
s_write_lines_d = write_lines_i;
s_write_state_d = WriteStart;
end
end
end
WriteStart: begin
if (s_write_start_valid && s_write_start_ready) begin
s_write_burst_beats_d = s_write_beats;
s_write_state_d = WriteData;
end
end
WriteData: begin
if (s_write_data_valid && s_write_data_ready) begin
s_write_addr_d = s_write_addr_q + s_write_beat_bytes;
s_write_remaining_d = s_write_remaining_q - s_write_beat_bytes;
if (s_write_burst_beats_q == 5'd1) begin
s_write_state_d = WriteResponse;
end else begin
s_write_burst_beats_d = s_write_burst_beats_q - 1'b1;
end
end
end
WriteResponse: begin
if (s_write_master_done) begin
if (s_write_abort_q || s_write_fault || s_err_q) begin
s_write_done_d = 1'b1;
s_write_state_d = WriteIdle;
end else if (s_write_remaining_q == 32'd0) begin
if (s_write_lines_q == 16'd1) begin
s_write_done_d = 1'b1;
s_write_state_d = WriteIdle;
end else begin
s_write_line_base_d = s_write_line_base_q + s_write_stride_q;
s_write_addr_d = s_write_line_base_q + s_write_stride_q;
s_write_remaining_d = s_write_line_bytes_q;
s_write_lines_d = s_write_lines_q - 1'b1;
s_write_state_d = WriteStart;
end
end else begin
s_write_state_d = WriteStart;
end
end
end
default: s_write_state_d = WriteIdle;
endcase
end
always_comb begin
s_err_d = s_err_q;
s_err_read_d = s_err_read_q;
s_err_resp_d = s_err_resp_q;
s_err_addr_d = s_err_addr_q;
s_read_bytes_d = s_read_bytes_q;
s_write_bytes_d = s_write_bytes_q;
s_read_stall_d = s_read_stall_q;
s_write_stall_d = s_write_stall_q;
if ((read_start_i || write_start_i) && !read_busy_o && !write_busy_o) begin
s_err_d = 1'b0;
s_err_read_d = 1'b0;
s_err_resp_d = `AXI4_RESP_OKAY;
s_err_addr_d = '0;
s_read_bytes_d = 32'd0;
s_write_bytes_d = 32'd0;
s_read_stall_d = 32'd0;
s_write_stall_d = 32'd0;
end
if (read_axis.tvalid && !read_axis.tready) begin
s_read_stall_d = saturating_increment(s_read_stall_q);
end
if (write_axis.tvalid && !write_axis.tready) begin
s_write_stall_d = saturating_increment(s_write_stall_q);
end
if (s_read_beat_valid && s_read_beat_ready && !s_read_fault && !s_read_abort_q) begin
s_read_bytes_d = s_read_bytes_q + s_read_beat_bytes;
end
if (s_write_data_valid && s_write_data_ready && !s_write_payload_fault &&
!s_write_abort_q) begin
s_write_bytes_d = s_write_bytes_q + s_write_beat_bytes;
end
if (!s_err_q && s_read_fault) begin
s_err_d = 1'b1;
s_err_read_d = 1'b1;
s_err_resp_d = s_read_resp;
s_err_addr_d = s_read_addr_q;
end else if (!s_err_q && (s_write_fault ||
((s_write_state_q == WriteData) && write_axis.tvalid &&
s_write_data_ready && s_write_payload_fault))) begin
s_err_d = 1'b1;
s_err_read_d = 1'b0;
s_err_resp_d = s_write_fault ? s_write_resp : `AXI4_RESP_SLAVE_ERROR;
s_err_addr_d = s_write_addr_q;
end else if (!s_err_q && abort_i && (read_busy_o || write_busy_o)) begin
s_err_d = 1'b1;
s_err_read_d = read_busy_o;
s_err_resp_d = `AXI4_RESP_OKAY;
s_err_addr_d = read_busy_o ? s_read_addr_q : s_write_addr_q;
end
end
dma_axi4_master #(
.AddrWidth (AddrWidth),
.DataWidth (DataWidth),
.MaxBurstBeats(MaxBurstBeats)
) u_axi4_master (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.read_start_valid_i (s_read_start_valid),
.read_start_ready_o (s_read_start_ready),
.read_addr_i (s_read_addr_q),
.read_beats_i (s_read_beats),
.read_fixed_i (1'b0),
.read_busy_o (s_read_master_busy),
.read_beat_valid_o (s_read_beat_valid),
.read_beat_ready_i (s_read_beat_ready),
.read_data_o (s_read_data),
.read_resp_o (s_read_resp),
.read_last_o (s_read_last),
.read_expected_last_o(s_read_expected_last),
.read_id_error_o (s_read_id_err),
.read_done_o (s_read_master_done),
.write_start_valid_i (s_write_start_valid),
.write_start_ready_o (s_write_start_ready),
.write_addr_i (s_write_addr_q),
.write_beats_i (s_write_beats),
.write_fixed_i (1'b0),
.write_busy_o (s_write_master_busy),
.write_data_valid_i (s_write_data_valid),
.write_data_ready_o (s_write_data_ready),
.write_data_i (s_write_data),
.write_strb_i (s_write_strobe),
.write_done_o (s_write_master_done),
.write_resp_o (s_write_resp),
.write_id_error_o (s_write_id_err),
.axi4 (axi4)
);
dffr #(
.DATA_WIDTH(2)
) u_read_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_state_d),
.dat_o (s_read_state_bits_q)
);
dffr #(
.DATA_WIDTH(2)
) u_write_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_state_d),
.dat_o (s_write_state_bits_q)
);
dffr #(
.DATA_WIDTH(AddrWidth)
) u_read_line_base_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_line_base_d),
.dat_o (s_read_line_base_q)
);
dffr #(
.DATA_WIDTH(AddrWidth)
) u_read_addr_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_addr_d),
.dat_o (s_read_addr_q)
);
dffr #(
.DATA_WIDTH(32)
) u_read_line_bytes_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_line_bytes_d),
.dat_o (s_read_line_bytes_q)
);
dffr #(
.DATA_WIDTH(32)
) u_read_remaining_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_remaining_d),
.dat_o (s_read_remaining_q)
);
dffr #(
.DATA_WIDTH(32)
) u_read_stride_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_stride_d),
.dat_o (s_read_stride_q)
);
dffr #(
.DATA_WIDTH(16)
) u_read_lines_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_lines_d),
.dat_o (s_read_lines_q)
);
dffr u_read_first_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_first_d),
.dat_o (s_read_first_q)
);
dffr u_read_abort_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_abort_d),
.dat_o (s_read_abort_q)
);
dffr u_read_done_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_done_d),
.dat_o (s_read_done_q)
);
dffr #(
.DATA_WIDTH(AddrWidth)
) u_write_line_base_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_line_base_d),
.dat_o (s_write_line_base_q)
);
dffr #(
.DATA_WIDTH(AddrWidth)
) u_write_addr_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_addr_d),
.dat_o (s_write_addr_q)
);
dffr #(
.DATA_WIDTH(32)
) u_write_line_bytes_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_line_bytes_d),
.dat_o (s_write_line_bytes_q)
);
dffr #(
.DATA_WIDTH(32)
) u_write_remaining_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_remaining_d),
.dat_o (s_write_remaining_q)
);
dffr #(
.DATA_WIDTH(32)
) u_write_stride_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_stride_d),
.dat_o (s_write_stride_q)
);
dffr #(
.DATA_WIDTH(16)
) u_write_lines_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_lines_d),
.dat_o (s_write_lines_q)
);
dffr #(
.DATA_WIDTH(5)
) u_write_burst_beats_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_burst_beats_d),
.dat_o (s_write_burst_beats_q)
);
dffr u_write_abort_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_abort_d),
.dat_o (s_write_abort_q)
);
dffr u_write_done_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_done_d),
.dat_o (s_write_done_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
dffr u_err_read_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_read_d),
.dat_o (s_err_read_q)
);
dffr #(
.DATA_WIDTH(2)
) u_err_resp_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_resp_d),
.dat_o (s_err_resp_q)
);
dffr #(
.DATA_WIDTH(AddrWidth)
) u_err_addr_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_addr_d),
.dat_o (s_err_addr_q)
);
dffr #(
.DATA_WIDTH(32)
) u_read_bytes_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_bytes_d),
.dat_o (s_read_bytes_q)
);
dffr #(
.DATA_WIDTH(32)
) u_write_bytes_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_bytes_d),
.dat_o (s_write_bytes_q)
);
dffr #(
.DATA_WIDTH(32)
) u_read_stall_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_stall_d),
.dat_o (s_read_stall_q)
);
dffr #(
.DATA_WIDTH(32)
) u_write_stall_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_write_stall_d),
.dat_o (s_write_stall_q)
);
`ifndef SYNTHESIS
initial begin
if ((AddrWidth != 32) || (DataWidth != 64) || (MaxBurstBeats < 1) || (MaxBurstBeats > 16)) begin
$fatal(1, "jpeg_dma: unsupported AXI geometry");
end
end
`endif
endmodule
+579
View File
@@ -0,0 +1,579 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_encode_core (
// verilog_format: off -- preserve command, tables, pixel input, and JPEG output columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic [15:0] width_i,
input logic [15:0] height_i,
input logic [ 1:0] sampling_i,
input logic [ 2:0] input_format_i,
input logic [15:0] restart_interval_i,
input logic [511:0] luma_quant_i,
input logic [1599:0] luma_reciprocal_i,
input logic [191:0] luma_dc_code_i,
input logic [ 59:0] luma_dc_length_i,
input logic [4095:0] luma_ac_code_i,
input logic [1279:0] luma_ac_length_i,
input logic [511:0] chroma_quant_i,
input logic [1599:0] chroma_reciprocal_i,
input logic [191:0] chroma_dc_code_i,
input logic [ 59:0] chroma_dc_length_i,
input logic [4095:0] chroma_ac_code_i,
input logic [1279:0] chroma_ac_length_i,
axi4_stream_if.sink pixel_axis,
axi4_stream_if.source bitstream_axis,
output logic start_ready_o,
output logic busy_o,
output logic done_o,
output logic error_o
// verilog_format: on
);
typedef enum logic [3:0] {
Idle,
Header,
McuStart,
BlockStart,
BlockWait,
FlushRequest,
FlushWait,
RestartMarker,
Eoi,
OutputDrain,
Done
} state_e;
state_e s_state_d;
state_e s_state_q;
logic [ 3:0] s_state_bits_q;
logic [ 15:0] s_width_d;
logic [ 15:0] s_width_q;
logic [ 15:0] s_height_d;
logic [ 15:0] s_height_q;
logic [ 1:0] s_sampling_d;
logic [ 1:0] s_sampling_q;
logic [ 2:0] s_format_d;
logic [ 2:0] s_format_q;
logic [ 15:0] s_restart_interval_d;
logic [ 15:0] s_restart_interval_q;
logic [ 15:0] s_restart_cnt_d;
logic [ 15:0] s_restart_cnt_q;
logic [ 2:0][23:0] s_dc_predictor_d;
logic [ 2:0][23:0] s_dc_predictor_q;
logic [ 11:0] s_mcu_col_d;
logic [ 11:0] s_mcu_col_q;
logic [ 11:0] s_mcu_row_d;
logic [ 11:0] s_mcu_row_q;
logic [ 2:0] s_component_d;
logic [ 2:0] s_component_q;
logic s_block_last_d;
logic s_block_last_q;
logic [ 2:0] s_restart_index_d;
logic [ 2:0] s_restart_index_q;
logic s_flush_restart_d;
logic s_flush_restart_q;
logic s_err_d;
logic s_err_q;
logic [ 4:0] s_mcu_width;
logic [ 4:0] s_mcu_height;
logic [ 11:0] s_mcu_columns;
logic [ 11:0] s_mcu_rows;
logic [ 15:0] s_mcu_x_base;
logic [ 15:0] s_mcu_y_base;
logic [ 4:0] s_valid_width;
logic [ 4:0] s_valid_height;
logic s_last_mcu;
logic [ 2:0] s_block_component;
logic s_header_start;
logic [ 7:0] s_header_byte;
logic s_header_valid;
logic s_header_ready;
logic s_header_done;
logic s_header_err;
logic s_builder_start;
logic s_builder_start_ready;
logic [ 511:0] s_builder_block;
logic s_builder_block_valid;
logic s_builder_block_ready;
logic s_builder_block_last;
logic s_builder_done;
logic s_builder_err;
logic s_block_encoder_start;
logic [ 127:0] s_token_bits;
logic [ 23:0] s_token_len;
logic [ 2:0] s_token_cnt;
logic s_token_valid;
logic s_token_last;
logic s_token_ready;
logic s_packer_token_ready;
logic [ 155:0] s_token_payload_in;
logic [ 155:0] s_token_payload_out;
logic s_token_spill_valid;
logic signed [ 23:0] s_block_dc;
logic s_block_result_valid;
logic s_block_result_ready;
logic s_block_err;
logic s_packer_flush;
logic s_packer_flush_ready;
logic s_packer_flush_done;
logic [ 63:0] s_packer_data;
logic [ 7:0] s_packer_keep;
logic s_packer_valid;
logic s_packer_last;
logic s_packer_ready;
logic s_packer_err;
logic [ 63:0] s_join_data;
logic [ 7:0] s_join_keep;
logic s_join_valid;
logic s_join_ready;
logic s_join_last;
logic s_join_err;
logic s_join_accept;
logic [ 511:0] s_selected_quant;
logic [1599:0] s_selected_reciprocal;
logic [ 191:0] s_selected_dc_code;
logic [ 59:0] s_selected_dc_len;
logic [4095:0] s_selected_ac_code;
logic [1279:0] s_selected_ac_len;
always_comb begin
unique case (s_sampling_q)
2'd0, 2'd1: begin
s_mcu_width = 5'd8;
s_mcu_height = 5'd8;
end
2'd2: begin
s_mcu_width = 5'd16;
s_mcu_height = 5'd8;
end
default: begin
s_mcu_width = 5'd16;
s_mcu_height = 5'd16;
end
endcase
s_mcu_columns = 12'((s_width_q + 16'(s_mcu_width) - 1'b1) / 16'(s_mcu_width));
s_mcu_rows = 12'((s_height_q + 16'(s_mcu_height) - 1'b1) / 16'(s_mcu_height));
s_mcu_x_base = s_mcu_col_q * s_mcu_width;
s_mcu_y_base = s_mcu_row_q * s_mcu_height;
s_valid_width = ((s_width_q - s_mcu_x_base) >= 16'(s_mcu_width)) ?
s_mcu_width : 5'(s_width_q - s_mcu_x_base);
s_valid_height = ((s_height_q - s_mcu_y_base) >= 16'(s_mcu_height)) ?
s_mcu_height : 5'(s_height_q - s_mcu_y_base);
s_last_mcu = (s_mcu_col_q + 1'b1 == s_mcu_columns) && (s_mcu_row_q + 1'b1 == s_mcu_rows);
if (s_sampling_q == 2'd0) begin
s_block_component = 3'd0;
end else if ((s_sampling_q == 2'd1) && (s_component_q != 3'd0)) begin
s_block_component = s_component_q;
end else if ((s_sampling_q == 2'd2) && (s_component_q >= 3'd2)) begin
s_block_component = s_component_q - 1'b1;
end else if ((s_sampling_q == 2'd3) && (s_component_q >= 3'd4)) begin
s_block_component = s_component_q - 3'd3;
end else begin
s_block_component = 3'd0;
end
end
assign s_selected_quant = (s_block_component == 3'd0) ? luma_quant_i : chroma_quant_i;
assign s_selected_reciprocal = (s_block_component == 3'd0) ?
luma_reciprocal_i : chroma_reciprocal_i;
assign s_selected_dc_code = (s_block_component == 3'd0) ? luma_dc_code_i : chroma_dc_code_i;
assign s_selected_dc_len = (s_block_component == 3'd0) ? luma_dc_length_i : chroma_dc_length_i;
assign s_selected_ac_code = (s_block_component == 3'd0) ? luma_ac_code_i : chroma_ac_code_i;
assign s_selected_ac_len = (s_block_component == 3'd0) ? luma_ac_length_i : chroma_ac_length_i;
assign s_state_q = state_e'(s_state_bits_q);
assign start_ready_o = s_state_q == Idle;
assign busy_o = (s_state_q != Idle) && (s_state_q != Done);
assign done_o = s_state_q == Done;
assign error_o = s_err_q;
assign s_header_start = (s_state_q == Idle) && start_i;
assign s_builder_start = (s_state_q == McuStart) && s_builder_start_ready;
assign s_block_encoder_start = (s_state_q == BlockStart) && s_builder_block_valid;
assign s_builder_block_ready = s_block_encoder_start;
assign s_block_result_ready = s_state_q == BlockWait;
assign s_token_payload_in = {s_token_last, s_token_cnt, s_token_len, s_token_bits};
assign s_packer_flush = (s_state_q == FlushRequest) && s_packer_flush_ready;
assign s_join_accept = s_join_valid && s_join_ready;
always_comb begin
s_join_data = 64'd0;
s_join_keep = 8'd0;
s_join_valid = 1'b0;
s_join_last = 1'b0;
s_header_ready = 1'b0;
s_packer_ready = 1'b0;
if (s_state_q == Header) begin
s_join_data[7:0] = s_header_byte;
s_join_keep = 8'h01;
s_join_valid = s_header_valid;
s_header_ready = s_join_ready;
end else if ((s_state_q == BlockStart) || (s_state_q == BlockWait) ||
(s_state_q == FlushRequest) || (s_state_q == FlushWait)) begin
s_join_data = s_packer_data;
s_join_keep = s_packer_keep;
s_join_valid = s_packer_valid;
s_packer_ready = s_join_ready;
end else if (s_state_q == RestartMarker) begin
s_join_data[15:0] = {8'(8'hd0 + s_restart_index_q), 8'hff};
s_join_keep = 8'h03;
s_join_valid = 1'b1;
end else if (s_state_q == Eoi) begin
s_join_data[15:0] = 16'hd9ff;
s_join_keep = 8'h03;
s_join_valid = 1'b1;
s_join_last = 1'b1;
end
end
always_comb begin
s_state_d = s_state_q;
s_width_d = s_width_q;
s_height_d = s_height_q;
s_sampling_d = s_sampling_q;
s_format_d = s_format_q;
s_restart_interval_d = s_restart_interval_q;
s_restart_cnt_d = s_restart_cnt_q;
s_dc_predictor_d = s_dc_predictor_q;
s_mcu_col_d = s_mcu_col_q;
s_mcu_row_d = s_mcu_row_q;
s_component_d = s_component_q;
s_block_last_d = s_block_last_q;
s_restart_index_d = s_restart_index_q;
s_flush_restart_d = s_flush_restart_q;
s_err_d = s_err_q;
unique case (s_state_q)
Idle: begin
if (start_i) begin
s_width_d = width_i;
s_height_d = height_i;
s_sampling_d = sampling_i;
s_format_d = input_format_i;
s_restart_interval_d = restart_interval_i;
s_restart_cnt_d = 16'd0;
s_dc_predictor_d = '0;
s_mcu_col_d = 12'd0;
s_mcu_row_d = 12'd0;
s_component_d = 3'd0;
s_restart_index_d = 3'd0;
s_flush_restart_d = 1'b0;
s_err_d = (width_i == 16'd0) || (height_i == 16'd0) ||
(width_i > 16'd2048) || (height_i > 16'd2048) ||
(input_format_i > 3'd4);
s_state_d = Header;
end
end
Header: begin
if (s_header_done) begin
s_err_d |= s_header_err;
s_state_d = McuStart;
end
end
McuStart: begin
if (s_builder_start_ready) begin
s_component_d = 3'd0;
s_state_d = BlockStart;
end
end
BlockStart: begin
if (s_block_encoder_start) begin
s_block_last_d = s_builder_block_last;
s_state_d = BlockWait;
end
end
BlockWait: begin
if (s_block_result_valid) begin
s_dc_predictor_d[s_block_component] = s_block_dc;
s_err_d |= s_block_err;
if (!s_block_last_q) begin
s_component_d = s_component_q + 1'b1;
s_state_d = BlockStart;
end else if (s_last_mcu) begin
s_flush_restart_d = 1'b0;
s_state_d = FlushRequest;
end else if ((s_restart_interval_q != 16'd0) &&
(s_restart_cnt_q + 1'b1 >= s_restart_interval_q)) begin
s_flush_restart_d = 1'b1;
s_state_d = FlushRequest;
end else begin
s_restart_cnt_d = s_restart_cnt_q + 1'b1;
if (s_mcu_col_q + 1'b1 == s_mcu_columns) begin
s_mcu_col_d = 12'd0;
s_mcu_row_d = s_mcu_row_q + 1'b1;
end else begin
s_mcu_col_d = s_mcu_col_q + 1'b1;
end
s_state_d = McuStart;
end
end
end
FlushRequest: begin
if (s_packer_flush_ready) begin
s_state_d = FlushWait;
end
end
FlushWait: begin
if (s_packer_flush_done) begin
if (s_flush_restart_q) begin
s_state_d = RestartMarker;
end else begin
s_state_d = Eoi;
end
end
end
RestartMarker: begin
if (s_join_accept) begin
s_dc_predictor_d = '0;
s_restart_cnt_d = 16'd0;
s_restart_index_d = s_restart_index_q + 1'b1;
if (s_mcu_col_q + 1'b1 == s_mcu_columns) begin
s_mcu_col_d = 12'd0;
s_mcu_row_d = s_mcu_row_q + 1'b1;
end else begin
s_mcu_col_d = s_mcu_col_q + 1'b1;
end
s_state_d = McuStart;
end
end
Eoi: begin
if (s_join_accept) begin
s_state_d = OutputDrain;
end
end
OutputDrain: begin
if (bitstream_axis.tvalid && bitstream_axis.tready && bitstream_axis.tlast) begin
s_err_d |= s_builder_err || s_packer_err || s_join_err;
s_state_d = Done;
end
end
Done: s_state_d = Idle;
default: s_state_d = Idle;
endcase
end
jpeg_header_writer u_header_writer (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_header_start),
.width_i (width_i),
.height_i (height_i),
.sampling_i (sampling_i),
.restart_interval_i(restart_interval_i),
.luma_quant_i (luma_quant_i),
.chroma_quant_i (chroma_quant_i),
.start_ready_o (),
.byte_o (s_header_byte),
.byte_valid_o (s_header_valid),
.byte_ready_i (s_header_ready),
.done_o (s_header_done),
.error_o (s_header_err)
);
jpeg_mcu_builder u_mcu_builder (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_builder_start),
.sampling_i (s_sampling_q),
.format_i (s_format_q),
.valid_width_i (s_valid_width),
.valid_height_i(s_valid_height),
.pixel_axis (pixel_axis),
.start_ready_o (s_builder_start_ready),
.block_o (s_builder_block),
.block_valid_o (s_builder_block_valid),
.block_ready_i (s_builder_block_ready),
.block_last_o (s_builder_block_last),
.done_o (s_builder_done),
.error_o (s_builder_err)
);
jpeg_block_encoder u_block_encoder (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_block_encoder_start),
.block_i (s_builder_block),
.previous_dc_i (s_dc_predictor_q[s_block_component]),
.quant_i (s_selected_quant),
.reciprocal_i (s_selected_reciprocal),
.dc_code_i (s_selected_dc_code),
.dc_length_i (s_selected_dc_len),
.ac_code_i (s_selected_ac_code),
.ac_length_i (s_selected_ac_len),
.start_ready_o (),
.token_bits_o (s_token_bits),
.token_length_o(s_token_len),
.token_count_o (s_token_cnt),
.token_valid_o (s_token_valid),
.token_last_o (s_token_last),
.token_ready_i (s_token_ready),
.dc_o (s_block_dc),
.result_valid_o(s_block_result_valid),
.result_ready_i(s_block_result_ready),
.error_o (s_block_err)
);
jpeg_bit_packer u_bit_packer (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.token_bits_i (s_token_payload_out[127:0]),
.token_length_i(s_token_payload_out[151:128]),
.token_count_i (s_token_payload_out[154:152]),
.token_valid_i (s_token_spill_valid),
.token_ready_o (s_packer_token_ready),
.flush_i (s_packer_flush),
.flush_ready_o (s_packer_flush_ready),
.flush_done_o (s_packer_flush_done),
.byte_data_o (s_packer_data),
.byte_keep_o (s_packer_keep),
.byte_valid_o (s_packer_valid),
.byte_last_o (s_packer_last),
.byte_ready_i (s_packer_ready),
.error_o (s_packer_err)
);
spill_register #(
.DATA_WIDTH(156),
.BYPASS (1'b0)
) u_token_spill (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.flush_i(1'b0),
.valid_i(s_token_valid),
.ready_o(s_token_ready),
.data_i (s_token_payload_in),
.valid_o(s_token_spill_valid),
.ready_i(s_packer_token_ready),
.data_o (s_token_payload_out)
);
jpeg_byte_joiner u_byte_joiner (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.input_data_i (s_join_data),
.input_keep_i (s_join_keep),
.input_valid_i(s_join_valid),
.input_ready_o(s_join_ready),
.input_last_i (s_join_last),
.output_axis (bitstream_axis),
.error_o (s_join_err)
);
dffr #(
.DATA_WIDTH(4)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(16)
) u_width_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_width_d),
.dat_o (s_width_q)
);
dffr #(
.DATA_WIDTH(16)
) u_height_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_height_d),
.dat_o (s_height_q)
);
dffr #(
.DATA_WIDTH(2)
) u_sampling_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_sampling_d),
.dat_o (s_sampling_q)
);
dffr #(
.DATA_WIDTH(3)
) u_format_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_format_d),
.dat_o (s_format_q)
);
dffr #(
.DATA_WIDTH(16)
) u_restart_interval_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_restart_interval_d),
.dat_o (s_restart_interval_q)
);
dffr #(
.DATA_WIDTH(16)
) u_restart_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_restart_cnt_d),
.dat_o (s_restart_cnt_q)
);
dffr #(
.DATA_WIDTH(3 * 24)
) u_dc_predictor_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dc_predictor_d),
.dat_o (s_dc_predictor_q)
);
dffr #(
.DATA_WIDTH(12)
) u_mcu_col_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_mcu_col_d),
.dat_o (s_mcu_col_q)
);
dffr #(
.DATA_WIDTH(12)
) u_mcu_row_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_mcu_row_d),
.dat_o (s_mcu_row_q)
);
dffr #(
.DATA_WIDTH(3)
) u_component_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_component_d),
.dat_o (s_component_q)
);
dffr u_block_last_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_block_last_d),
.dat_o (s_block_last_q)
);
dffr #(
.DATA_WIDTH(3)
) u_restart_index_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_restart_index_d),
.dat_o (s_restart_index_q)
);
dffr u_flush_restart_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_flush_restart_d),
.dat_o (s_flush_restart_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
endmodule
+313
View File
@@ -0,0 +1,313 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_entropy_decoder #(
parameter int unsigned ElementWidth = 24,
parameter int unsigned WindowWidth = 32
) (
// verilog_format: off -- preserve block, table, and bit-window interface columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic signed [ElementWidth-1:0] previous_dc_i,
input logic [12*16-1:0] dc_code_i,
input logic [12*5-1:0] dc_length_i,
input logic [256*16-1:0] ac_code_i,
input logic [256*5-1:0] ac_length_i,
input logic [WindowWidth-1:0] bit_window_i,
input logic [$clog2(WindowWidth+1)-1:0] bit_count_i,
output logic [$clog2(WindowWidth+1)-1:0] bit_consume_o,
output logic bit_consume_valid_o,
output logic start_ready_o,
output logic [64*ElementWidth-1:0] block_o,
output logic signed [ElementWidth-1:0] dc_o,
output logic result_valid_o,
input logic result_ready_i,
output logic error_o
// verilog_format: on
);
typedef enum logic [1:0] {
Idle,
Dc,
Ac,
Result
} state_e;
localparam int unsigned CountWidth = $clog2(WindowWidth + 1);
state_e s_state_d;
state_e s_state_q;
logic [ 1:0] s_state_bits_q;
logic [ 6:0] s_coeff_index_d;
logic [ 6:0] s_coeff_index_q;
logic [64*ElementWidth-1:0] s_block_d;
logic [64*ElementWidth-1:0] s_block_q;
logic signed [ ElementWidth-1:0] s_dc_d;
logic signed [ ElementWidth-1:0] s_dc_q;
logic s_err_d;
logic s_err_q;
logic s_match;
logic [ 7:0] s_symbol;
logic [ 4:0] s_code_len;
logic [ 15:0] s_code;
logic [ 4:0] s_value_len;
logic [ CountWidth-1:0] s_total_len;
logic [ ElementWidth-1:0] s_amplitude;
logic signed [ ElementWidth-1:0] s_value;
logic [ 3:0] s_run;
logic [ 6:0] s_next_index;
function automatic logic [5:0] zigzag_index(input logic [5:0] index_i);
begin
unique case (index_i)
6'd0: zigzag_index = 6'd0;
6'd1: zigzag_index = 6'd1;
6'd2: zigzag_index = 6'd8;
6'd3: zigzag_index = 6'd16;
6'd4: zigzag_index = 6'd9;
6'd5: zigzag_index = 6'd2;
6'd6: zigzag_index = 6'd3;
6'd7: zigzag_index = 6'd10;
6'd8: zigzag_index = 6'd17;
6'd9: zigzag_index = 6'd24;
6'd10: zigzag_index = 6'd32;
6'd11: zigzag_index = 6'd25;
6'd12: zigzag_index = 6'd18;
6'd13: zigzag_index = 6'd11;
6'd14: zigzag_index = 6'd4;
6'd15: zigzag_index = 6'd5;
6'd16: zigzag_index = 6'd12;
6'd17: zigzag_index = 6'd19;
6'd18: zigzag_index = 6'd26;
6'd19: zigzag_index = 6'd33;
6'd20: zigzag_index = 6'd40;
6'd21: zigzag_index = 6'd48;
6'd22: zigzag_index = 6'd41;
6'd23: zigzag_index = 6'd34;
6'd24: zigzag_index = 6'd27;
6'd25: zigzag_index = 6'd20;
6'd26: zigzag_index = 6'd13;
6'd27: zigzag_index = 6'd6;
6'd28: zigzag_index = 6'd7;
6'd29: zigzag_index = 6'd14;
6'd30: zigzag_index = 6'd21;
6'd31: zigzag_index = 6'd28;
6'd32: zigzag_index = 6'd35;
6'd33: zigzag_index = 6'd42;
6'd34: zigzag_index = 6'd49;
6'd35: zigzag_index = 6'd56;
6'd36: zigzag_index = 6'd57;
6'd37: zigzag_index = 6'd50;
6'd38: zigzag_index = 6'd43;
6'd39: zigzag_index = 6'd36;
6'd40: zigzag_index = 6'd29;
6'd41: zigzag_index = 6'd22;
6'd42: zigzag_index = 6'd15;
6'd43: zigzag_index = 6'd23;
6'd44: zigzag_index = 6'd30;
6'd45: zigzag_index = 6'd37;
6'd46: zigzag_index = 6'd44;
6'd47: zigzag_index = 6'd51;
6'd48: zigzag_index = 6'd58;
6'd49: zigzag_index = 6'd59;
6'd50: zigzag_index = 6'd52;
6'd51: zigzag_index = 6'd45;
6'd52: zigzag_index = 6'd38;
6'd53: zigzag_index = 6'd31;
6'd54: zigzag_index = 6'd39;
6'd55: zigzag_index = 6'd46;
6'd56: zigzag_index = 6'd53;
6'd57: zigzag_index = 6'd60;
6'd58: zigzag_index = 6'd61;
6'd59: zigzag_index = 6'd54;
6'd60: zigzag_index = 6'd47;
6'd61: zigzag_index = 6'd55;
6'd62: zigzag_index = 6'd62;
default: zigzag_index = 6'd63;
endcase
end
endfunction
function automatic logic signed [ElementWidth-1:0] extend_value(
input logic [ElementWidth-1:0] amplitude_i, input logic [4:0] length_i);
logic [ElementWidth-1:0] s_threshold;
logic [ElementWidth-1:0] s_mask;
begin
if (length_i == 5'd0) begin
return '0;
end
s_threshold = ElementWidth'(1) << (length_i - 1'b1);
s_mask = (ElementWidth'(1) << length_i) - 1'b1;
return amplitude_i >= s_threshold ? $signed(amplitude_i) : $signed(amplitude_i | ~s_mask);
end
endfunction
assign s_state_q = state_e'(s_state_bits_q);
assign start_ready_o = s_state_q == Idle;
assign block_o = s_block_q;
assign dc_o = s_dc_q;
assign result_valid_o = s_state_q == Result;
assign error_o = s_err_q;
always_comb begin
s_match = 1'b0;
s_symbol = 8'd0;
s_code_len = 5'd0;
s_code = 16'd0;
if (s_state_q == Dc) begin
for (int unsigned symbol = 0; symbol < 12; symbol++) begin
if (!s_match && (dc_length_i[symbol*5+:5] != 5'd0) &&
(6'(dc_length_i[symbol*5+:5]) <= bit_count_i) &&
(dc_code_i[symbol*16+:16] ==
16'(bit_window_i >> (WindowWidth - int'(dc_length_i[symbol*5+:5]))))) begin
s_match = 1'b1;
s_symbol = 8'(symbol);
s_code_len = dc_length_i[symbol*5+:5];
s_code = dc_code_i[symbol*16+:16];
end
end
end else if (s_state_q == Ac) begin
for (int unsigned symbol = 0; symbol < 256; symbol++) begin
if (!s_match && (ac_length_i[symbol*5+:5] != 5'd0) &&
(6'(ac_length_i[symbol*5+:5]) <= bit_count_i) &&
(ac_code_i[symbol*16+:16] ==
16'(bit_window_i >> (WindowWidth - int'(ac_length_i[symbol*5+:5]))))) begin
s_match = 1'b1;
s_symbol = 8'(symbol);
s_code_len = ac_length_i[symbol*5+:5];
s_code = ac_code_i[symbol*16+:16];
end
end
end
end
always_comb begin
s_state_d = s_state_q;
s_coeff_index_d = s_coeff_index_q;
s_block_d = s_block_q;
s_dc_d = s_dc_q;
s_err_d = s_err_q;
bit_consume_o = '0;
bit_consume_valid_o = 1'b0;
s_value_len = {1'b0, s_symbol[3:0]};
s_total_len = CountWidth'(s_code_len + s_value_len);
s_amplitude = ElementWidth'(
bit_window_i >> (WindowWidth - int'(s_code_len) - int'(s_value_len))
) & ((ElementWidth'(1) << s_value_len) - 1'b1);
s_value = extend_value(s_amplitude, s_value_len);
s_run = s_symbol[7:4];
s_next_index = s_coeff_index_q + {3'd0, s_run};
unique case (s_state_q)
Idle: begin
if (start_i) begin
s_coeff_index_d = 7'd1;
s_block_d = '0;
s_dc_d = previous_dc_i;
s_err_d = 1'b0;
s_state_d = Dc;
end
end
Dc: begin
if (s_match && (s_symbol <= 8'd11) && (s_total_len <= bit_count_i)) begin
bit_consume_o = s_total_len;
bit_consume_valid_o = 1'b1;
s_dc_d = s_dc_q + s_value;
s_block_d[0+:ElementWidth] = s_dc_q + s_value;
s_state_d = Ac;
end else if (bit_count_i >= CountWidth'(16)) begin
s_err_d = 1'b1;
s_state_d = Result;
end
end
Ac: begin
if (s_match && (s_total_len <= bit_count_i)) begin
if (s_symbol == 8'h00) begin
bit_consume_o = CountWidth'(s_code_len);
bit_consume_valid_o = 1'b1;
s_state_d = Result;
end else if (s_symbol == 8'hf0) begin
bit_consume_o = CountWidth'(s_code_len);
bit_consume_valid_o = 1'b1;
if (s_coeff_index_q > 7'd48) begin
s_err_d = 1'b1;
s_state_d = Result;
end else begin
s_coeff_index_d = s_coeff_index_q + 7'd16;
end
end else if ((s_value_len == 5'd0) || (s_value_len > 5'd10) ||
(s_next_index >= 7'd64)) begin
s_err_d = 1'b1;
s_state_d = Result;
end else begin
bit_consume_o = s_total_len;
bit_consume_valid_o = 1'b1;
s_block_d[zigzag_index(s_next_index[5:0])*ElementWidth+:ElementWidth] = s_value;
if (s_next_index == 7'd63) begin
s_state_d = Result;
end else begin
s_coeff_index_d = s_next_index + 1'b1;
end
end
end else if (bit_count_i >= CountWidth'(16)) begin
s_err_d = 1'b1;
s_state_d = Result;
end
end
Result: begin
if (result_ready_i) begin
s_state_d = Idle;
end
end
default: s_state_d = Idle;
endcase
end
dffr #(
.DATA_WIDTH(2)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(7)
) u_coeff_index_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_coeff_index_d),
.dat_o (s_coeff_index_q)
);
dffr #(
.DATA_WIDTH(64 * ElementWidth)
) u_block_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_block_d),
.dat_o (s_block_q)
);
dffr #(
.DATA_WIDTH(ElementWidth)
) u_dc_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dc_d),
.dat_o (s_dc_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
`ifndef SYNTHESIS
initial begin
if (ElementWidth < 16 || WindowWidth < 27) begin
$fatal(1, "jpeg_entropy_decoder: invalid coefficient or window width");
end
end
`endif
endmodule
+393
View File
@@ -0,0 +1,393 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_entropy_encoder #(
parameter int unsigned ElementWidth = 24,
parameter int unsigned TokenSlots = 4,
parameter int unsigned TokenWidth = 32
) (
// verilog_format: off -- preserve block, table, and token interface columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic [64*ElementWidth-1:0] block_i,
input logic signed [ElementWidth-1:0] previous_dc_i,
input logic [12*16-1:0] dc_code_i,
input logic [12*5-1:0] dc_length_i,
input logic [256*16-1:0] ac_code_i,
input logic [256*5-1:0] ac_length_i,
output logic start_ready_o,
output logic signed [ElementWidth-1:0] dc_o,
output logic [TokenSlots*TokenWidth-1:0] token_bits_o,
output logic [TokenSlots*6-1:0] token_length_o,
output logic [$clog2(TokenSlots+1)-1:0] token_count_o,
output logic token_valid_o,
output logic token_last_o,
input logic token_ready_i,
output logic error_o
// verilog_format: on
);
localparam int unsigned EmitCountWidth = $clog2(TokenSlots + 1);
typedef enum logic [1:0] {
Idle,
Dc,
Ac
} state_e;
state_e s_state_d;
state_e s_state_q;
logic [ 1:0] s_state_bits_q;
logic [ 3:0] s_group_d;
logic [ 3:0] s_group_q;
logic [ 3:0] s_run_d;
logic [ 3:0] s_run_q;
logic [ 64*ElementWidth-1:0] s_block_d;
logic [ 64*ElementWidth-1:0] s_block_q;
logic signed [ ElementWidth-1:0] s_previous_dc_d;
logic signed [ ElementWidth-1:0] s_previous_dc_q;
logic signed [ ElementWidth-1:0] s_dc_d;
logic signed [ ElementWidth-1:0] s_dc_q;
logic s_err_d;
logic s_err_q;
logic [ 3:0] s_run_work;
logic [$clog2(TokenSlots+1)-1:0] s_emit_count;
logic signed [ ElementWidth-1:0] s_coefficient_work;
logic [ 4:0] s_category_work;
logic [ 7:0] s_symbol_work;
logic [ 15:0] s_code_work;
logic [ 4:0] s_code_len_work;
logic [ ElementWidth-1:0] s_amplitude_work;
logic [ 5:0] s_total_len_work;
function automatic logic [5:0] zigzag_index(input logic [5:0] index_i);
begin
unique case (index_i)
6'd0: zigzag_index = 6'd0;
6'd1: zigzag_index = 6'd1;
6'd2: zigzag_index = 6'd8;
6'd3: zigzag_index = 6'd16;
6'd4: zigzag_index = 6'd9;
6'd5: zigzag_index = 6'd2;
6'd6: zigzag_index = 6'd3;
6'd7: zigzag_index = 6'd10;
6'd8: zigzag_index = 6'd17;
6'd9: zigzag_index = 6'd24;
6'd10: zigzag_index = 6'd32;
6'd11: zigzag_index = 6'd25;
6'd12: zigzag_index = 6'd18;
6'd13: zigzag_index = 6'd11;
6'd14: zigzag_index = 6'd4;
6'd15: zigzag_index = 6'd5;
6'd16: zigzag_index = 6'd12;
6'd17: zigzag_index = 6'd19;
6'd18: zigzag_index = 6'd26;
6'd19: zigzag_index = 6'd33;
6'd20: zigzag_index = 6'd40;
6'd21: zigzag_index = 6'd48;
6'd22: zigzag_index = 6'd41;
6'd23: zigzag_index = 6'd34;
6'd24: zigzag_index = 6'd27;
6'd25: zigzag_index = 6'd20;
6'd26: zigzag_index = 6'd13;
6'd27: zigzag_index = 6'd6;
6'd28: zigzag_index = 6'd7;
6'd29: zigzag_index = 6'd14;
6'd30: zigzag_index = 6'd21;
6'd31: zigzag_index = 6'd28;
6'd32: zigzag_index = 6'd35;
6'd33: zigzag_index = 6'd42;
6'd34: zigzag_index = 6'd49;
6'd35: zigzag_index = 6'd56;
6'd36: zigzag_index = 6'd57;
6'd37: zigzag_index = 6'd50;
6'd38: zigzag_index = 6'd43;
6'd39: zigzag_index = 6'd36;
6'd40: zigzag_index = 6'd29;
6'd41: zigzag_index = 6'd22;
6'd42: zigzag_index = 6'd15;
6'd43: zigzag_index = 6'd23;
6'd44: zigzag_index = 6'd30;
6'd45: zigzag_index = 6'd37;
6'd46: zigzag_index = 6'd44;
6'd47: zigzag_index = 6'd51;
6'd48: zigzag_index = 6'd58;
6'd49: zigzag_index = 6'd59;
6'd50: zigzag_index = 6'd52;
6'd51: zigzag_index = 6'd45;
6'd52: zigzag_index = 6'd38;
6'd53: zigzag_index = 6'd31;
6'd54: zigzag_index = 6'd39;
6'd55: zigzag_index = 6'd46;
6'd56: zigzag_index = 6'd53;
6'd57: zigzag_index = 6'd60;
6'd58: zigzag_index = 6'd61;
6'd59: zigzag_index = 6'd54;
6'd60: zigzag_index = 6'd47;
6'd61: zigzag_index = 6'd55;
6'd62: zigzag_index = 6'd62;
default: zigzag_index = 6'd63;
endcase
end
endfunction
function automatic logic [4:0] value_category(input logic signed [ElementWidth-1:0] value_i);
logic [ElementWidth-1:0] s_magnitude;
logic [ 4:0] s_category;
begin
s_magnitude = value_i < 0 ? ElementWidth'(-value_i) : ElementWidth'(value_i);
s_category = 5'd0;
for (int unsigned index = 0; index < ElementWidth; index++) begin
if (s_magnitude[index]) begin
s_category = 5'(index + 1);
end
end
return s_category;
end
endfunction
function automatic logic [ElementWidth-1:0] amplitude_bits(
input logic signed [ElementWidth-1:0] value_i, input logic [4:0] category_i);
logic [ElementWidth-1:0] s_mask;
begin
s_mask = (ElementWidth'(1) << category_i) - 1'b1;
return value_i < 0 ? ElementWidth'(value_i) & s_mask : ElementWidth'(value_i);
end
endfunction
function automatic logic has_nonzero_after(input logic [64*ElementWidth-1:0] block_i,
input logic [5:0] scan_index_i);
logic s_found;
begin
s_found = 1'b0;
for (int unsigned index = 1; index < 64; index++) begin
if ((index > scan_index_i) && ($signed(
block_i[zigzag_index(6'(index))*ElementWidth+:ElementWidth]
) != 0)) begin
s_found = 1'b1;
end
end
return s_found;
end
endfunction
function automatic logic [TokenWidth-1:0] compose_token(
input logic [15:0] code_i, input logic [ElementWidth-1:0] amplitude_i,
input logic [4:0] category_i);
logic [TokenWidth-1:0] s_code;
logic [TokenWidth-1:0] s_amplitude;
logic [TokenWidth-1:0] s_mask;
begin
s_code = TokenWidth'(code_i);
s_amplitude = TokenWidth'(amplitude_i);
s_mask = (TokenWidth'(1) << category_i) - 1'b1;
return (s_code << category_i) | (s_amplitude & s_mask);
end
endfunction
assign s_state_q = state_e'(s_state_bits_q);
assign start_ready_o = s_state_q == Idle;
assign dc_o = s_dc_q;
assign token_count_o = s_emit_count;
assign token_valid_o = s_emit_count != '0;
assign error_o = s_err_q;
always_comb begin
s_state_d = s_state_q;
s_group_d = s_group_q;
s_run_d = s_run_q;
s_block_d = s_block_q;
s_previous_dc_d = s_previous_dc_q;
s_dc_d = s_dc_q;
s_err_d = s_err_q;
token_bits_o = '0;
token_length_o = '0;
token_last_o = 1'b0;
s_emit_count = '0;
s_run_work = s_run_q;
s_coefficient_work = '0;
s_category_work = '0;
s_symbol_work = '0;
s_code_work = '0;
s_code_len_work = '0;
s_amplitude_work = '0;
s_total_len_work = '0;
unique case (s_state_q)
Idle: begin
s_group_d = 4'd0;
s_run_d = 4'd0;
if (start_i) begin
s_block_d = block_i;
s_previous_dc_d = previous_dc_i;
s_dc_d = block_i[0+:ElementWidth];
s_err_d = 1'b0;
s_state_d = Dc;
end
end
Dc: begin
s_coefficient_work = s_dc_q - s_previous_dc_q;
s_category_work = value_category(s_coefficient_work);
if (s_category_work > 5'd11) begin
s_err_d = 1'b1;
end else begin
s_code_work = dc_code_i[s_category_work*16+:16];
s_code_len_work = dc_length_i[s_category_work*5+:5];
s_amplitude_work = amplitude_bits(s_coefficient_work, s_category_work);
s_total_len_work = {1'b0, s_code_len_work} + s_category_work;
token_bits_o[0+:TokenWidth] =
compose_token(s_code_work, s_amplitude_work, s_category_work);
token_length_o[0+:6] = s_total_len_work;
s_emit_count = EmitCountWidth'(1);
if (s_code_len_work == 5'd0) begin
s_err_d = 1'b1;
end
end
if ((s_emit_count == '0) || token_ready_i) begin
s_state_d = Ac;
end
end
Ac: begin
for (int unsigned lane = 0; lane < 4; lane++) begin
if (((s_group_q * 4) + lane + 1) < 64) begin
s_coefficient_work =
s_block_q[zigzag_index(6'((s_group_q*4)+lane+1))*ElementWidth+:ElementWidth];
if (s_coefficient_work == 0) begin
if ((s_run_work == 4'd15) && has_nonzero_after(
s_block_q, 6'((s_group_q * 4) + lane + 1)
)) begin
s_code_work = ac_code_i[8'hf0*16+:16];
s_code_len_work = ac_length_i[8'hf0*5+:5];
if (s_emit_count < EmitCountWidth'(TokenSlots)) begin
token_bits_o[s_emit_count*TokenWidth+:TokenWidth] = TokenWidth'(s_code_work);
token_length_o[s_emit_count*6+:6] = {1'b0, s_code_len_work};
s_emit_count += 1'b1;
end else begin
s_err_d = 1'b1;
end
if (s_code_len_work == 5'd0) begin
s_err_d = 1'b1;
end
s_run_work = 4'd0;
end else if (s_run_work != 4'd15) begin
s_run_work += 1'b1;
end
end else begin
s_category_work = value_category(s_coefficient_work);
s_symbol_work = {s_run_work, s_category_work[3:0]};
s_code_work = ac_code_i[s_symbol_work*16+:16];
s_code_len_work = ac_length_i[s_symbol_work*5+:5];
s_amplitude_work = amplitude_bits(s_coefficient_work, s_category_work);
s_total_len_work = {1'b0, s_code_len_work} + s_category_work;
if ((s_category_work == 5'd0) || (s_category_work > 5'd10) ||
(s_code_len_work == 5'd0)) begin
s_err_d = 1'b1;
end else if (s_emit_count < EmitCountWidth'(TokenSlots)) begin
token_bits_o[s_emit_count*TokenWidth+:TokenWidth] =
compose_token(s_code_work, s_amplitude_work, s_category_work);
token_length_o[s_emit_count*6+:6] = s_total_len_work;
s_emit_count += 1'b1;
end else begin
s_err_d = 1'b1;
end
s_run_work = 4'd0;
end
end
end
if (s_group_q == 4'd15 && s_run_work != 4'd0) begin
s_code_work = ac_code_i[0+:16];
s_code_len_work = ac_length_i[0+:5];
if (s_emit_count < EmitCountWidth'(TokenSlots)) begin
token_bits_o[s_emit_count*TokenWidth+:TokenWidth] = TokenWidth'(s_code_work);
token_length_o[s_emit_count*6+:6] = {1'b0, s_code_len_work};
s_emit_count += 1'b1;
end else begin
s_err_d = 1'b1;
end
if (s_code_len_work == 5'd0) begin
s_err_d = 1'b1;
end
s_run_work = 4'd0;
end
token_last_o = s_group_q == 4'd15;
if (s_emit_count == '0) begin
s_run_d = s_run_work;
s_group_d = s_group_q + 1'b1;
end else if (token_ready_i) begin
s_run_d = s_run_work;
if (s_group_q == 4'd15) begin
s_state_d = Idle;
end else begin
s_group_d = s_group_q + 1'b1;
end
end
end
default: begin
s_state_d = Idle;
end
endcase
end
dffr #(
.DATA_WIDTH(2)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(4)
) u_group_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_group_d),
.dat_o (s_group_q)
);
dffr #(
.DATA_WIDTH(4)
) u_run_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_run_d),
.dat_o (s_run_q)
);
dffr #(
.DATA_WIDTH(64 * ElementWidth)
) u_block_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_block_d),
.dat_o (s_block_q)
);
dffr #(
.DATA_WIDTH(ElementWidth)
) u_previous_dc_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_previous_dc_d),
.dat_o (s_previous_dc_q)
);
dffr #(
.DATA_WIDTH(ElementWidth)
) u_dc_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dc_d),
.dat_o (s_dc_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
`ifndef SYNTHESIS
initial begin
if (TokenSlots < 4 || TokenWidth < 27 || ElementWidth < 16) begin
$fatal(1, "jpeg_entropy_encoder: invalid token or coefficient width");
end
end
`endif
endmodule
+377
View File
@@ -0,0 +1,377 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_header_writer (
// verilog_format: off -- preserve command, quantization table, and byte columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic [15:0] width_i,
input logic [15:0] height_i,
input logic [ 1:0] sampling_i,
input logic [15:0] restart_interval_i,
input logic [511:0] luma_quant_i,
input logic [511:0] chroma_quant_i,
output logic start_ready_o,
output logic [ 7:0] byte_o,
output logic byte_valid_o,
input logic byte_ready_i,
output logic done_o,
output logic error_o
// verilog_format: on
);
localparam int unsigned GrayDhtBytes = 212;
localparam int unsigned ColorDhtBytes = 420;
localparam logic [GrayDhtBytes*8-1:0] GrayDht = {
256'hfaf9f8f7f6f5f4f3f2f1eae9e8e7e6e5e4e3e2e1dad9d8d7d6d5d4d3d2cac9c8,
256'hc7c6c5c4c3c2bab9b8b7b6b5b4b3b2aaa9a8a7a6a5a4a3a29a99989796959493,
256'h928a898887868584837a797877767574736a696867666564635a595857565554,
256'h534a494847464544433a3938373635342a29282726251a191817160a09827262,
256'h3324f0d15215c1b1422308a19181321471220761511306413121120511040003,
256'h02017d010000040405050304020303010200100b0a0908070605040302010000,
160'h00000000000001010101010105010000d200c4ff
};
localparam logic [ColorDhtBytes*8-1:0] ColorDht = {
256'hfaf9f8f7f6f5f4f3f2eae9e8e7e6e5e4e3e2dad9d8d7d6d5d4d3d2cac9c8c7c6,
256'hc5c4c3c2bab9b8b7b6b5b4b3b2aaa9a8a7a6a5a4a3a29a99989796959493928a,
256'h89888786858483827a797877767574736a696867666564635a59585756555453,
256'h4a494847464544433a39383736352a292827261a191817f125e13424160ad172,
256'h6215f052332309c1b1a191421408813222137161075141120631210504110302,
256'h010077020100040405070403040402010200110b0a0908070605040302010000,
256'h00000000010101010101010101030001faf9f8f7f6f5f4f3f2f1eae9e8e7e6e5,
256'he4e3e2e1dad9d8d7d6d5d4d3d2cac9c8c7c6c5c4c3c2bab9b8b7b6b5b4b3b2aa,
256'ha9a8a7a6a5a4a3a29a99989796959493928a898887868584837a797877767574,
256'h736a696867666564635a595857565554534a494847464544433a393837363534,
256'h2a29282726251a191817160a098272623324f0d15215c1b1422308a191813214,
256'h7122076151130641312112051104000302017d01000004040505030402030301,
256'h0200100b0a090807060504030201000000000000000001010101010105010000,
32'ha201c4ff
};
logic s_active_d;
logic s_active_q;
logic [ 9:0] s_position_d;
logic [ 9:0] s_position_q;
logic [ 15:0] s_width_d;
logic [ 15:0] s_width_q;
logic [ 15:0] s_height_d;
logic [ 15:0] s_height_q;
logic [ 1:0] s_sampling_d;
logic [ 1:0] s_sampling_q;
logic [ 15:0] s_restart_d;
logic [ 15:0] s_restart_q;
logic [511:0] s_luma_quant_d;
logic [511:0] s_luma_quant_q;
logic [511:0] s_chroma_quant_d;
logic [511:0] s_chroma_quant_q;
logic s_done_d;
logic s_done_q;
logic s_err_d;
logic s_err_q;
logic s_gray;
logic [ 9:0] s_dqt_start;
logic [ 9:0] s_dqt_bytes;
logic [ 9:0] s_sof_start;
logic [ 9:0] s_sof_bytes;
logic [ 9:0] s_dht_start;
logic [ 9:0] s_dht_bytes;
logic [ 9:0] s_dri_start;
logic [ 9:0] s_dri_bytes;
logic [ 9:0] s_sos_start;
logic [ 9:0] s_sos_bytes;
logic [ 9:0] s_total_bytes;
logic [ 9:0] s_relative;
function automatic logic [5:0] zigzag_index(input logic [5:0] index_i);
begin
unique case (index_i)
6'd0: zigzag_index = 6'd0;
6'd1: zigzag_index = 6'd1;
6'd2: zigzag_index = 6'd8;
6'd3: zigzag_index = 6'd16;
6'd4: zigzag_index = 6'd9;
6'd5: zigzag_index = 6'd2;
6'd6: zigzag_index = 6'd3;
6'd7: zigzag_index = 6'd10;
6'd8: zigzag_index = 6'd17;
6'd9: zigzag_index = 6'd24;
6'd10: zigzag_index = 6'd32;
6'd11: zigzag_index = 6'd25;
6'd12: zigzag_index = 6'd18;
6'd13: zigzag_index = 6'd11;
6'd14: zigzag_index = 6'd4;
6'd15: zigzag_index = 6'd5;
6'd16: zigzag_index = 6'd12;
6'd17: zigzag_index = 6'd19;
6'd18: zigzag_index = 6'd26;
6'd19: zigzag_index = 6'd33;
6'd20: zigzag_index = 6'd40;
6'd21: zigzag_index = 6'd48;
6'd22: zigzag_index = 6'd41;
6'd23: zigzag_index = 6'd34;
6'd24: zigzag_index = 6'd27;
6'd25: zigzag_index = 6'd20;
6'd26: zigzag_index = 6'd13;
6'd27: zigzag_index = 6'd6;
6'd28: zigzag_index = 6'd7;
6'd29: zigzag_index = 6'd14;
6'd30: zigzag_index = 6'd21;
6'd31: zigzag_index = 6'd28;
6'd32: zigzag_index = 6'd35;
6'd33: zigzag_index = 6'd42;
6'd34: zigzag_index = 6'd49;
6'd35: zigzag_index = 6'd56;
6'd36: zigzag_index = 6'd57;
6'd37: zigzag_index = 6'd50;
6'd38: zigzag_index = 6'd43;
6'd39: zigzag_index = 6'd36;
6'd40: zigzag_index = 6'd29;
6'd41: zigzag_index = 6'd22;
6'd42: zigzag_index = 6'd15;
6'd43: zigzag_index = 6'd23;
6'd44: zigzag_index = 6'd30;
6'd45: zigzag_index = 6'd37;
6'd46: zigzag_index = 6'd44;
6'd47: zigzag_index = 6'd51;
6'd48: zigzag_index = 6'd58;
6'd49: zigzag_index = 6'd59;
6'd50: zigzag_index = 6'd52;
6'd51: zigzag_index = 6'd45;
6'd52: zigzag_index = 6'd38;
6'd53: zigzag_index = 6'd31;
6'd54: zigzag_index = 6'd39;
6'd55: zigzag_index = 6'd46;
6'd56: zigzag_index = 6'd53;
6'd57: zigzag_index = 6'd60;
6'd58: zigzag_index = 6'd61;
6'd59: zigzag_index = 6'd54;
6'd60: zigzag_index = 6'd47;
6'd61: zigzag_index = 6'd55;
6'd62: zigzag_index = 6'd62;
default: zigzag_index = 6'd63;
endcase
end
endfunction
assign s_gray = s_sampling_q == 2'd0;
assign s_dqt_start = 10'd20;
assign s_dqt_bytes = s_gray ? 10'd69 : 10'd134;
assign s_sof_start = s_dqt_start + s_dqt_bytes;
assign s_sof_bytes = s_gray ? 10'd13 : 10'd19;
assign s_dht_start = s_sof_start + s_sof_bytes;
assign s_dht_bytes = s_gray ? 10'(GrayDhtBytes) : 10'(ColorDhtBytes);
assign s_dri_start = s_dht_start + s_dht_bytes;
assign s_dri_bytes = (s_restart_q == 16'd0) ? 10'd0 : 10'd6;
assign s_sos_start = s_dri_start + s_dri_bytes;
assign s_sos_bytes = s_gray ? 10'd10 : 10'd14;
assign s_total_bytes = s_sos_start + s_sos_bytes;
assign start_ready_o = !s_active_q;
assign byte_valid_o = s_active_q;
assign done_o = s_done_q;
assign error_o = s_err_q;
always_comb begin
byte_o = 8'd0;
s_relative = 10'd0;
if (s_position_q == 10'd0) begin
byte_o = 8'hff;
end else if (s_position_q == 10'd1) begin
byte_o = 8'hd8;
end else if (s_position_q < 10'd20) begin
s_relative = s_position_q - 10'd2;
unique case (s_relative)
10'd0: byte_o = 8'hff;
10'd1: byte_o = 8'he0;
10'd2: byte_o = 8'h00;
10'd3: byte_o = 8'h10;
10'd4: byte_o = 8'h4a;
10'd5: byte_o = 8'h46;
10'd6: byte_o = 8'h49;
10'd7: byte_o = 8'h46;
10'd8: byte_o = 8'h00;
10'd9: byte_o = 8'h01;
10'd10: byte_o = 8'h02;
10'd11: byte_o = 8'h00;
10'd13, 10'd15: byte_o = 8'h01;
default: byte_o = 8'h00;
endcase
end else if (s_position_q < s_sof_start) begin
s_relative = s_position_q - s_dqt_start;
if (s_relative == 10'd0) byte_o = 8'hff;
else if (s_relative == 10'd1) byte_o = 8'hdb;
else if (s_relative == 10'd2) byte_o = 8'h00;
else if (s_relative == 10'd3) byte_o = s_gray ? 8'h43 : 8'h84;
else if (s_relative == 10'd4) byte_o = 8'h00;
else if (s_relative < 10'd69) begin
byte_o = s_luma_quant_q[zigzag_index(6'(s_relative-5))*8+:8];
end else if (s_relative == 10'd69) begin
byte_o = 8'h01;
end else begin
byte_o = s_chroma_quant_q[zigzag_index(6'(s_relative-70))*8+:8];
end
end else if (s_position_q < s_dht_start) begin
s_relative = s_position_q - s_sof_start;
if (s_relative == 10'd0) byte_o = 8'hff;
else if (s_relative == 10'd1) byte_o = 8'hc0;
else if (s_relative == 10'd2) byte_o = 8'h00;
else if (s_relative == 10'd3) byte_o = s_gray ? 8'h0b : 8'h11;
else if (s_relative == 10'd4) byte_o = 8'h08;
else if (s_relative == 10'd5) byte_o = s_height_q[15:8];
else if (s_relative == 10'd6) byte_o = s_height_q[7:0];
else if (s_relative == 10'd7) byte_o = s_width_q[15:8];
else if (s_relative == 10'd8) byte_o = s_width_q[7:0];
else if (s_relative == 10'd9) byte_o = s_gray ? 8'd1 : 8'd3;
else if (s_relative == 10'd10) byte_o = 8'd1;
else if (s_relative == 10'd11) begin
unique case (s_sampling_q)
2'd0, 2'd1: byte_o = 8'h11;
2'd2: byte_o = 8'h21;
default: byte_o = 8'h22;
endcase
end else if (s_relative == 10'd12) byte_o = 8'd0;
else if (s_relative == 10'd13) byte_o = 8'd2;
else if (s_relative == 10'd14) byte_o = 8'h11;
else if (s_relative == 10'd15) byte_o = 8'd1;
else if (s_relative == 10'd16) byte_o = 8'd3;
else if (s_relative == 10'd17) byte_o = 8'h11;
else byte_o = 8'd1;
end else if (s_position_q < s_dri_start) begin
s_relative = s_position_q - s_dht_start;
byte_o = s_gray ? GrayDht[s_relative*8+:8] : ColorDht[s_relative*8+:8];
end else if (s_position_q < s_sos_start) begin
s_relative = s_position_q - s_dri_start;
unique case (s_relative)
10'd0: byte_o = 8'hff;
10'd1: byte_o = 8'hdd;
10'd2: byte_o = 8'h00;
10'd3: byte_o = 8'h04;
10'd4: byte_o = s_restart_q[15:8];
default: byte_o = s_restart_q[7:0];
endcase
end else begin
s_relative = s_position_q - s_sos_start;
if (s_relative == 10'd0) byte_o = 8'hff;
else if (s_relative == 10'd1) byte_o = 8'hda;
else if (s_relative == 10'd2) byte_o = 8'h00;
else if (s_relative == 10'd3) byte_o = s_gray ? 8'h08 : 8'h0c;
else if (s_relative == 10'd4) byte_o = s_gray ? 8'd1 : 8'd3;
else if (s_relative == 10'd5) byte_o = 8'd1;
else if (s_relative == 10'd6) byte_o = 8'h00;
else if (!s_gray && s_relative == 10'd7) byte_o = 8'd2;
else if (!s_gray && s_relative == 10'd8) byte_o = 8'h11;
else if (!s_gray && s_relative == 10'd9) byte_o = 8'd3;
else if (!s_gray && s_relative == 10'd10) byte_o = 8'h11;
else if (s_relative == s_sos_bytes - 3) byte_o = 8'd0;
else if (s_relative == s_sos_bytes - 2) byte_o = 8'd63;
else byte_o = 8'd0;
end
end
always_comb begin
s_active_d = s_active_q;
s_position_d = s_position_q;
s_width_d = s_width_q;
s_height_d = s_height_q;
s_sampling_d = s_sampling_q;
s_restart_d = s_restart_q;
s_luma_quant_d = s_luma_quant_q;
s_chroma_quant_d = s_chroma_quant_q;
s_done_d = 1'b0;
s_err_d = s_err_q;
if (start_i && start_ready_o) begin
s_width_d = width_i;
s_height_d = height_i;
s_sampling_d = sampling_i;
s_restart_d = restart_interval_i;
s_luma_quant_d = luma_quant_i;
s_chroma_quant_d = chroma_quant_i;
s_position_d = 10'd0;
s_err_d = (width_i == 16'd0) || (height_i == 16'd0);
s_active_d = 1'b1;
end
if (byte_valid_o && byte_ready_i) begin
if (s_position_q + 1'b1 == s_total_bytes) begin
s_active_d = 1'b0;
s_done_d = 1'b1;
end else begin
s_position_d = s_position_q + 1'b1;
end
end
end
dffr u_active_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_active_d),
.dat_o (s_active_q)
);
dffr #(
.DATA_WIDTH(10)
) u_position_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_position_d),
.dat_o (s_position_q)
);
dffr #(
.DATA_WIDTH(16)
) u_width_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_width_d),
.dat_o (s_width_q)
);
dffr #(
.DATA_WIDTH(16)
) u_height_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_height_d),
.dat_o (s_height_q)
);
dffr #(
.DATA_WIDTH(2)
) u_sampling_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_sampling_d),
.dat_o (s_sampling_q)
);
dffr #(
.DATA_WIDTH(16)
) u_restart_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_restart_d),
.dat_o (s_restart_q)
);
dffr #(
.DATA_WIDTH(512)
) u_luma_quant_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_luma_quant_d),
.dat_o (s_luma_quant_q)
);
dffr #(
.DATA_WIDTH(512)
) u_chroma_quant_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_chroma_quant_d),
.dat_o (s_chroma_quant_q)
);
dffr u_done_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_done_d),
.dat_o (s_done_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
endmodule
+875
View File
@@ -0,0 +1,875 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_marker_parser (
// verilog_format: off -- preserve byte, table, header, and error interface columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic [ 1:0] table_context_i,
input logic [ 7:0] byte_i,
input logic byte_valid_i,
output logic byte_ready_o,
output logic [ 1:0] table_context_o,
output logic [ 3:0] table_kind_o,
output logic [ 7:0] table_index_o,
output logic [31:0] table_data_o,
output logic table_write_o,
output logic table_commit_o,
output logic header_valid_o,
input logic header_ready_i,
output logic entropy_o,
output logic [15:0] width_o,
output logic [15:0] height_o,
output logic [ 1:0] sampling_o,
output logic [ 1:0] component_count_o,
output logic [23:0] component_id_o,
output logic [23:0] component_factor_o,
output logic [ 5:0] quant_table_o,
output logic [ 5:0] dc_table_o,
output logic [ 5:0] ac_table_o,
output logic [15:0] restart_interval_o,
output logic [31:0] marker_count_o,
output logic error_o,
output logic [ 4:0] error_code_o
// verilog_format: on
);
typedef enum logic [4:0] {
Idle,
SoiPrefix,
SoiCode,
MarkerPrefix,
MarkerCode,
LengthHigh,
LengthLow,
Skip,
DqtInfo,
DqtData,
DhtInfo,
DhtCounts,
DhtAdvance,
DhtValues,
SofData,
DriData,
SosData,
HeaderReady,
Entropy,
Error
} state_e;
localparam logic [4:0] ErrorMarker = 5'd4;
localparam logic [4:0] ErrorProcess = 5'd5;
localparam logic [4:0] ErrorPrecision = 5'd6;
localparam logic [4:0] ErrorSampling = 5'd7;
localparam logic [4:0] ErrorLength = 5'd8;
localparam logic [4:0] ErrorHuffman = 5'd9;
localparam logic [4:0] ErrorTruncated = 5'd10;
state_e s_state_d;
state_e s_state_q;
logic [ 4:0] s_state_bits_q;
logic [ 1:0] s_context_d;
logic [ 1:0] s_context_q;
logic [ 7:0] s_marker_d;
logic [ 7:0] s_marker_q;
logic [ 7:0] s_len_high_d;
logic [ 7:0] s_len_high_q;
logic [15:0] s_remaining_d;
logic [15:0] s_remaining_q;
logic [15:0] s_payload_index_d;
logic [15:0] s_payload_index_q;
logic [15:0] s_width_d;
logic [15:0] s_width_q;
logic [15:0] s_height_d;
logic [15:0] s_height_q;
logic [ 1:0] s_component_cnt_d;
logic [ 1:0] s_component_cnt_q;
logic [ 2:0][7:0] s_component_id_d;
logic [ 2:0][7:0] s_component_id_q;
logic [ 2:0][7:0] s_component_factor_d;
logic [ 2:0][7:0] s_component_factor_q;
logic [ 2:0][1:0] s_quant_table_d;
logic [ 2:0][1:0] s_quant_table_q;
logic [ 2:0][1:0] s_dc_table_d;
logic [ 2:0][1:0] s_dc_table_q;
logic [ 2:0][1:0] s_ac_table_d;
logic [ 2:0][1:0] s_ac_table_q;
logic [15:0] s_restart_interval_d;
logic [15:0] s_restart_interval_q;
logic [31:0] s_marker_cnt_d;
logic [31:0] s_marker_cnt_q;
logic [ 7:0] s_dqt_info_d;
logic [ 7:0] s_dqt_info_q;
logic [ 6:0] s_table_index_d;
logic [ 6:0] s_table_index_q;
logic [ 7:0] s_dht_info_d;
logic [ 7:0] s_dht_info_q;
logic [15:0][7:0] s_huff_counts_d;
logic [15:0][7:0] s_huff_counts_q;
logic [ 4:0] s_huff_len_d;
logic [ 4:0] s_huff_len_q;
logic [ 8:0] s_huff_values_d;
logic [ 8:0] s_huff_values_q;
logic [ 7:0] s_huff_len_remaining_d;
logic [ 7:0] s_huff_len_remaining_q;
logic [15:0] s_huff_code_d;
logic [15:0] s_huff_code_q;
logic [ 7:0] s_sos_ss_d;
logic [ 7:0] s_sos_ss_q;
logic [ 7:0] s_sos_se_d;
logic [ 7:0] s_sos_se_q;
logic s_err_d;
logic s_err_q;
logic [ 4:0] s_err_code_d;
logic [ 4:0] s_err_code_q;
logic [15:0] s_segment_len;
logic s_byte_accept;
logic [ 1:0] s_component_index;
logic [ 1:0] s_sos_component_index;
function automatic logic [5:0] zigzag_index(input logic [5:0] index_i);
begin
unique case (index_i)
6'd0: zigzag_index = 6'd0;
6'd1: zigzag_index = 6'd1;
6'd2: zigzag_index = 6'd8;
6'd3: zigzag_index = 6'd16;
6'd4: zigzag_index = 6'd9;
6'd5: zigzag_index = 6'd2;
6'd6: zigzag_index = 6'd3;
6'd7: zigzag_index = 6'd10;
6'd8: zigzag_index = 6'd17;
6'd9: zigzag_index = 6'd24;
6'd10: zigzag_index = 6'd32;
6'd11: zigzag_index = 6'd25;
6'd12: zigzag_index = 6'd18;
6'd13: zigzag_index = 6'd11;
6'd14: zigzag_index = 6'd4;
6'd15: zigzag_index = 6'd5;
6'd16: zigzag_index = 6'd12;
6'd17: zigzag_index = 6'd19;
6'd18: zigzag_index = 6'd26;
6'd19: zigzag_index = 6'd33;
6'd20: zigzag_index = 6'd40;
6'd21: zigzag_index = 6'd48;
6'd22: zigzag_index = 6'd41;
6'd23: zigzag_index = 6'd34;
6'd24: zigzag_index = 6'd27;
6'd25: zigzag_index = 6'd20;
6'd26: zigzag_index = 6'd13;
6'd27: zigzag_index = 6'd6;
6'd28: zigzag_index = 6'd7;
6'd29: zigzag_index = 6'd14;
6'd30: zigzag_index = 6'd21;
6'd31: zigzag_index = 6'd28;
6'd32: zigzag_index = 6'd35;
6'd33: zigzag_index = 6'd42;
6'd34: zigzag_index = 6'd49;
6'd35: zigzag_index = 6'd56;
6'd36: zigzag_index = 6'd57;
6'd37: zigzag_index = 6'd50;
6'd38: zigzag_index = 6'd43;
6'd39: zigzag_index = 6'd36;
6'd40: zigzag_index = 6'd29;
6'd41: zigzag_index = 6'd22;
6'd42: zigzag_index = 6'd15;
6'd43: zigzag_index = 6'd23;
6'd44: zigzag_index = 6'd30;
6'd45: zigzag_index = 6'd37;
6'd46: zigzag_index = 6'd44;
6'd47: zigzag_index = 6'd51;
6'd48: zigzag_index = 6'd58;
6'd49: zigzag_index = 6'd59;
6'd50: zigzag_index = 6'd52;
6'd51: zigzag_index = 6'd45;
6'd52: zigzag_index = 6'd38;
6'd53: zigzag_index = 6'd31;
6'd54: zigzag_index = 6'd39;
6'd55: zigzag_index = 6'd46;
6'd56: zigzag_index = 6'd53;
6'd57: zigzag_index = 6'd60;
6'd58: zigzag_index = 6'd61;
6'd59: zigzag_index = 6'd54;
6'd60: zigzag_index = 6'd47;
6'd61: zigzag_index = 6'd55;
6'd62: zigzag_index = 6'd62;
default: zigzag_index = 6'd63;
endcase
end
endfunction
function automatic logic [31:0] saturating_increment(input logic [31:0] value_i);
return (&value_i) ? value_i : value_i + 1'b1;
endfunction
assign s_state_q = state_e'(s_state_bits_q);
assign byte_ready_o = (s_state_q != Idle) && (s_state_q != DhtAdvance) &&
(s_state_q != HeaderReady) && (s_state_q != Entropy) &&
(s_state_q != Error);
assign s_byte_accept = byte_valid_i && byte_ready_o;
assign s_segment_len = {s_len_high_q, byte_i};
assign header_valid_o = s_state_q == HeaderReady;
assign entropy_o = s_state_q == Entropy;
assign width_o = s_width_q;
assign height_o = s_height_q;
assign component_count_o = s_component_cnt_q;
assign component_id_o = s_component_id_q;
assign component_factor_o = s_component_factor_q;
assign quant_table_o = s_quant_table_q;
assign dc_table_o = s_dc_table_q;
assign ac_table_o = s_ac_table_q;
assign restart_interval_o = s_restart_interval_q;
assign marker_count_o = s_marker_cnt_q;
assign error_o = s_err_q;
assign error_code_o = s_err_code_q;
assign table_context_o = s_context_q;
assign sampling_o = (s_component_cnt_q == 2'd1) ? 2'd0 :
(s_component_factor_q[0] == 8'h11) ? 2'd1 :
(s_component_factor_q[0] == 8'h21) ? 2'd2 : 2'd3;
always_comb begin
s_component_index = '0;
if (s_payload_index_q >= 16'd6) begin
s_component_index = 2'((int'(s_payload_index_q) - 6) / 3);
end
s_sos_component_index = 2'((int'(s_payload_index_q) - 1) / 2);
end
always_comb begin
s_state_d = s_state_q;
s_context_d = s_context_q;
s_marker_d = s_marker_q;
s_len_high_d = s_len_high_q;
s_remaining_d = s_remaining_q;
s_payload_index_d = s_payload_index_q;
s_width_d = s_width_q;
s_height_d = s_height_q;
s_component_cnt_d = s_component_cnt_q;
s_component_id_d = s_component_id_q;
s_component_factor_d = s_component_factor_q;
s_quant_table_d = s_quant_table_q;
s_dc_table_d = s_dc_table_q;
s_ac_table_d = s_ac_table_q;
s_restart_interval_d = s_restart_interval_q;
s_marker_cnt_d = s_marker_cnt_q;
s_dqt_info_d = s_dqt_info_q;
s_table_index_d = s_table_index_q;
s_dht_info_d = s_dht_info_q;
s_huff_counts_d = s_huff_counts_q;
s_huff_len_d = s_huff_len_q;
s_huff_values_d = s_huff_values_q;
s_huff_len_remaining_d = s_huff_len_remaining_q;
s_huff_code_d = s_huff_code_q;
s_sos_ss_d = s_sos_ss_q;
s_sos_se_d = s_sos_se_q;
s_err_d = s_err_q;
s_err_code_d = s_err_code_q;
table_kind_o = 4'd0;
table_index_o = 8'd0;
table_data_o = 32'd0;
table_write_o = 1'b0;
table_commit_o = 1'b0;
if (start_i) begin
s_state_d = SoiPrefix;
s_context_d = table_context_i;
s_remaining_d = 16'd0;
s_payload_index_d = 16'd0;
s_width_d = 16'd0;
s_height_d = 16'd0;
s_component_cnt_d = 2'd0;
s_component_id_d = '0;
s_component_factor_d = '0;
s_quant_table_d = '0;
s_dc_table_d = '0;
s_ac_table_d = '0;
s_restart_interval_d = 16'd0;
s_marker_cnt_d = 32'd0;
s_err_d = 1'b0;
s_err_code_d = 5'd0;
end else begin
unique case (s_state_q)
Idle: begin
end
SoiPrefix: begin
if (s_byte_accept) begin
if (byte_i == 8'hff) begin
s_state_d = SoiCode;
end else begin
s_err_d = 1'b1;
s_err_code_d = ErrorMarker;
s_state_d = Error;
end
end
end
SoiCode: begin
if (s_byte_accept) begin
if (byte_i == 8'hd8) begin
s_marker_cnt_d = 32'd1;
s_state_d = MarkerPrefix;
end else begin
s_err_d = 1'b1;
s_err_code_d = ErrorMarker;
s_state_d = Error;
end
end
end
MarkerPrefix: begin
if (s_byte_accept && (byte_i == 8'hff)) begin
s_state_d = MarkerCode;
end else if (s_byte_accept) begin
s_err_d = 1'b1;
s_err_code_d = ErrorMarker;
s_state_d = Error;
end
end
MarkerCode: begin
if (s_byte_accept) begin
if (byte_i == 8'hff) begin
s_state_d = MarkerCode;
end else if ((byte_i == 8'hd8) || (byte_i == 8'hd9) ||
((byte_i >= 8'hd0) && (byte_i <= 8'hd7))) begin
s_err_d = 1'b1;
s_err_code_d = ErrorMarker;
s_state_d = Error;
end else if ((byte_i == 8'hc1) || (byte_i == 8'hc2) ||
(byte_i == 8'hc3) || ((byte_i >= 8'hc5) && (byte_i <= 8'hcf))) begin
s_err_d = 1'b1;
s_err_code_d = ErrorProcess;
s_state_d = Error;
end else if (byte_i == 8'hdc) begin
s_err_d = 1'b1;
s_err_code_d = ErrorProcess;
s_state_d = Error;
end else begin
s_marker_d = byte_i;
s_marker_cnt_d = saturating_increment(s_marker_cnt_q);
s_state_d = LengthHigh;
end
end
end
LengthHigh: begin
if (s_byte_accept) begin
s_len_high_d = byte_i;
s_state_d = LengthLow;
end
end
LengthLow: begin
if (s_byte_accept) begin
if (s_segment_len < 16'd2) begin
s_err_d = 1'b1;
s_err_code_d = ErrorLength;
s_state_d = Error;
end else begin
s_remaining_d = s_segment_len - 16'd2;
s_payload_index_d = 16'd0;
unique case (s_marker_q)
8'hdb: s_state_d = DqtInfo;
8'hc4: s_state_d = DhtInfo;
8'hc0: s_state_d = SofData;
8'hdd: s_state_d = DriData;
8'hda: s_state_d = SosData;
default: begin
if ((s_marker_q >= 8'he0 && s_marker_q <= 8'hef) || (s_marker_q == 8'hfe)) begin
if (s_segment_len == 16'd2) begin
s_state_d = MarkerPrefix;
end else begin
s_state_d = Skip;
end
end else begin
s_err_d = 1'b1;
s_err_code_d = ErrorMarker;
s_state_d = Error;
end
end
endcase
end
end
end
Skip: begin
if (s_byte_accept) begin
s_remaining_d = s_remaining_q - 1'b1;
if (s_remaining_q == 16'd1) begin
s_state_d = MarkerPrefix;
end
end
end
DqtInfo: begin
if (s_byte_accept) begin
if ((byte_i[7:4] != 4'd0) || (byte_i[3:0] > 4'd3) || (s_remaining_q < 16'd65)) begin
s_err_d = 1'b1;
s_err_code_d = ErrorPrecision;
s_state_d = Error;
end else begin
s_dqt_info_d = byte_i;
s_table_index_d = 7'd0;
s_remaining_d = s_remaining_q - 1'b1;
s_state_d = DqtData;
end
end
end
DqtData: begin
if (s_byte_accept) begin
if (byte_i == 8'd0) begin
s_err_d = 1'b1;
s_err_code_d = ErrorPrecision;
s_state_d = Error;
end else begin
table_kind_o = s_dqt_info_q[3:0];
table_index_o = {2'd0, zigzag_index(s_table_index_q[5:0])};
table_data_o = {24'd0, byte_i};
table_write_o = 1'b1;
s_remaining_d = s_remaining_q - 1'b1;
if (s_table_index_q == 7'd63) begin
s_table_index_d = 7'd0;
if (s_remaining_q == 16'd1) begin
s_state_d = MarkerPrefix;
end else begin
s_state_d = DqtInfo;
end
end else begin
s_table_index_d = s_table_index_q + 1'b1;
end
end
end
end
DhtInfo: begin
if (s_byte_accept) begin
if ((byte_i[7:4] > 4'd1) || (byte_i[3:0] > 4'd3) || (s_remaining_q < 16'd18)) begin
s_err_d = 1'b1;
s_err_code_d = ErrorHuffman;
s_state_d = Error;
end else begin
s_dht_info_d = byte_i;
s_huff_counts_d = '0;
s_table_index_d = 7'd0;
s_huff_values_d = 9'd0;
s_remaining_d = s_remaining_q - 1'b1;
s_state_d = DhtCounts;
end
end
end
DhtCounts: begin
if (s_byte_accept) begin
s_huff_counts_d[s_table_index_q[3:0]] = byte_i;
s_huff_values_d = s_huff_values_q + 9'(byte_i);
s_remaining_d = s_remaining_q - 1'b1;
if (s_table_index_q == 7'd15) begin
if ((s_huff_values_q + 9'(byte_i) == 9'd0) ||
(s_huff_values_q + 9'(byte_i) > 9'd256) ||
(s_remaining_q - 1'b1 < 16'(s_huff_values_q + 9'(byte_i)))) begin
s_err_d = 1'b1;
s_err_code_d = ErrorHuffman;
s_state_d = Error;
end else begin
s_huff_len_d = 5'd1;
s_huff_code_d = 16'd0;
s_state_d = DhtAdvance;
end
end else begin
s_table_index_d = s_table_index_q + 1'b1;
end
end
end
DhtAdvance: begin
if (s_huff_values_q == 9'd0) begin
if (s_remaining_q == 16'd0) begin
s_state_d = MarkerPrefix;
end else begin
s_state_d = DhtInfo;
end
end else if (s_huff_len_q > 5'd16) begin
s_err_d = 1'b1;
s_err_code_d = ErrorHuffman;
s_state_d = Error;
end else if (s_huff_counts_q[s_huff_len_q-1'b1] != 8'd0) begin
s_huff_len_remaining_d = s_huff_counts_q[s_huff_len_q-1'b1];
s_state_d = DhtValues;
end else begin
s_huff_code_d = s_huff_code_q << 1;
s_huff_len_d = s_huff_len_q + 1'b1;
end
end
DhtValues: begin
if (s_byte_accept) begin
table_kind_o = (s_dht_info_q[7:4] != 4'd0) ?
(4'd8 + s_dht_info_q[3:0]) :
(4'd4 + s_dht_info_q[3:0]);
table_index_o = byte_i;
table_data_o = {11'd0, s_huff_len_q, s_huff_code_q};
table_write_o = 1'b1;
s_huff_code_d = s_huff_code_q + 1'b1;
s_huff_values_d = s_huff_values_q - 1'b1;
s_huff_len_remaining_d = s_huff_len_remaining_q - 1'b1;
s_remaining_d = s_remaining_q - 1'b1;
if (s_huff_values_q == 9'd1) begin
if (s_remaining_q == 16'd1) begin
s_state_d = MarkerPrefix;
end else begin
s_state_d = DhtInfo;
end
end else if (s_huff_len_remaining_q == 8'd1) begin
s_huff_code_d = (s_huff_code_q + 1'b1) << 1;
s_huff_len_d = s_huff_len_q + 1'b1;
s_state_d = DhtAdvance;
end
end
end
SofData: begin
if (s_byte_accept) begin
s_remaining_d = s_remaining_q - 1'b1;
unique case (s_payload_index_q)
16'd0: begin
if (byte_i != 8'd8) begin
s_err_d = 1'b1;
s_err_code_d = ErrorPrecision;
s_state_d = Error;
end
end
16'd1: s_height_d[15:8] = byte_i;
16'd2: s_height_d[7:0] = byte_i;
16'd3: s_width_d[15:8] = byte_i;
16'd4: s_width_d[7:0] = byte_i;
16'd5: begin
if ((byte_i != 8'd1) && (byte_i != 8'd3)) begin
s_err_d = 1'b1;
s_err_code_d = ErrorSampling;
s_state_d = Error;
end else begin
s_component_cnt_d = byte_i[1:0];
end
end
default: begin
if (((s_payload_index_q - 16'd6) % 3) == 0) begin
s_component_id_d[s_component_index] = byte_i;
end else if (((s_payload_index_q - 16'd6) % 3) == 1) begin
s_component_factor_d[s_component_index] = byte_i;
end else if (byte_i > 8'd3) begin
s_err_d = 1'b1;
s_err_code_d = ErrorSampling;
s_state_d = Error;
end else begin
s_quant_table_d[s_component_index] = byte_i[1:0];
end
end
endcase
s_payload_index_d = s_payload_index_q + 1'b1;
if ((s_remaining_q == 16'd1) && (s_state_d != Error)) begin
if ((s_width_d == 16'd0) || (s_height_d == 16'd0) ||
(s_payload_index_q + 1'b1 != 16'(6 + (3 * s_component_cnt_q))) ||
((s_component_cnt_q == 2'd1) && (s_component_factor_d[0] != 8'h11)) ||
((s_component_cnt_q == 2'd3) &&
!((s_component_factor_d[0] == 8'h11) ||
(s_component_factor_d[0] == 8'h21) ||
(s_component_factor_d[0] == 8'h22))) ||
((s_component_cnt_q == 2'd3) &&
((s_component_factor_d[1] != 8'h11) ||
(s_component_factor_d[2] != 8'h11)))) begin
s_err_d = 1'b1;
s_err_code_d = ErrorLength;
s_state_d = Error;
end else begin
s_state_d = MarkerPrefix;
end
end
end
end
DriData: begin
if (s_byte_accept) begin
if (s_remaining_q == 16'd2) begin
s_restart_interval_d[15:8] = byte_i;
end else if (s_remaining_q == 16'd1) begin
s_restart_interval_d[7:0] = byte_i;
s_state_d = MarkerPrefix;
end else begin
s_err_d = 1'b1;
s_err_code_d = ErrorLength;
s_state_d = Error;
end
s_remaining_d = s_remaining_q - 1'b1;
end
end
SosData: begin
if (s_byte_accept) begin
s_remaining_d = s_remaining_q - 1'b1;
if (s_payload_index_q == 16'd0) begin
if (byte_i != {6'd0, s_component_cnt_q}) begin
s_err_d = 1'b1;
s_err_code_d = ErrorSampling;
s_state_d = Error;
end
end else if (s_payload_index_q <= (2 * s_component_cnt_q)) begin
if (s_payload_index_q[0]) begin
if (byte_i != s_component_id_q[s_sos_component_index]) begin
s_err_d = 1'b1;
s_err_code_d = ErrorSampling;
s_state_d = Error;
end
end else if ((byte_i[7:4] > 4'd3) || (byte_i[3:0] > 4'd3)) begin
s_err_d = 1'b1;
s_err_code_d = ErrorHuffman;
s_state_d = Error;
end else begin
s_dc_table_d[s_sos_component_index] = byte_i[5:4];
s_ac_table_d[s_sos_component_index] = byte_i[1:0];
end
end else if (s_payload_index_q == (1 + (2 * s_component_cnt_q))) begin
s_sos_ss_d = byte_i;
end else if (s_payload_index_q == (2 + (2 * s_component_cnt_q))) begin
s_sos_se_d = byte_i;
end else if (s_payload_index_q == (3 + (2 * s_component_cnt_q))) begin
if ((s_sos_ss_q != 8'd0) || (s_sos_se_q != 8'd63) || (byte_i != 8'd0) ||
(s_remaining_q != 16'd1)) begin
s_err_d = 1'b1;
s_err_code_d = ErrorProcess;
s_state_d = Error;
end else begin
table_commit_o = 1'b1;
s_state_d = HeaderReady;
end
end else begin
s_err_d = 1'b1;
s_err_code_d = ErrorLength;
s_state_d = Error;
end
s_payload_index_d = s_payload_index_q + 1'b1;
end
end
HeaderReady: begin
if (header_ready_i) begin
s_state_d = Entropy;
end
end
Entropy: begin
end
Error: begin
end
default: s_state_d = Error;
endcase
end
end
dffr #(
.DATA_WIDTH(5)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(2)
) u_context_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_context_d),
.dat_o (s_context_q)
);
dffr #(
.DATA_WIDTH(8)
) u_marker_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_marker_d),
.dat_o (s_marker_q)
);
dffr #(
.DATA_WIDTH(8)
) u_len_high_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_len_high_d),
.dat_o (s_len_high_q)
);
dffr #(
.DATA_WIDTH(16)
) u_remaining_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_remaining_d),
.dat_o (s_remaining_q)
);
dffr #(
.DATA_WIDTH(16)
) u_payload_index_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_payload_index_d),
.dat_o (s_payload_index_q)
);
dffr #(
.DATA_WIDTH(16)
) u_width_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_width_d),
.dat_o (s_width_q)
);
dffr #(
.DATA_WIDTH(16)
) u_height_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_height_d),
.dat_o (s_height_q)
);
dffr #(
.DATA_WIDTH(2)
) u_component_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_component_cnt_d),
.dat_o (s_component_cnt_q)
);
dffr #(
.DATA_WIDTH(24)
) u_component_id_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_component_id_d),
.dat_o (s_component_id_q)
);
dffr #(
.DATA_WIDTH(24)
) u_component_factor_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_component_factor_d),
.dat_o (s_component_factor_q)
);
dffr #(
.DATA_WIDTH(6)
) u_quant_table_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_quant_table_d),
.dat_o (s_quant_table_q)
);
dffr #(
.DATA_WIDTH(6)
) u_dc_table_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dc_table_d),
.dat_o (s_dc_table_q)
);
dffr #(
.DATA_WIDTH(6)
) u_ac_table_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_ac_table_d),
.dat_o (s_ac_table_q)
);
dffr #(
.DATA_WIDTH(16)
) u_restart_interval_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_restart_interval_d),
.dat_o (s_restart_interval_q)
);
dffr #(
.DATA_WIDTH(32)
) u_marker_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_marker_cnt_d),
.dat_o (s_marker_cnt_q)
);
dffr #(
.DATA_WIDTH(8)
) u_dqt_info_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dqt_info_d),
.dat_o (s_dqt_info_q)
);
dffr #(
.DATA_WIDTH(7)
) u_table_index_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_table_index_d),
.dat_o (s_table_index_q)
);
dffr #(
.DATA_WIDTH(8)
) u_dht_info_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dht_info_d),
.dat_o (s_dht_info_q)
);
dffr #(
.DATA_WIDTH(16 * 8)
) u_huff_counts_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_huff_counts_d),
.dat_o (s_huff_counts_q)
);
dffr #(
.DATA_WIDTH(5)
) u_huff_len_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_huff_len_d),
.dat_o (s_huff_len_q)
);
dffr #(
.DATA_WIDTH(9)
) u_huff_values_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_huff_values_d),
.dat_o (s_huff_values_q)
);
dffr #(
.DATA_WIDTH(8)
) u_huff_len_remaining_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_huff_len_remaining_d),
.dat_o (s_huff_len_remaining_q)
);
dffr #(
.DATA_WIDTH(16)
) u_huff_code_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_huff_code_d),
.dat_o (s_huff_code_q)
);
dffr #(
.DATA_WIDTH(8)
) u_sos_ss_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_sos_ss_d),
.dat_o (s_sos_ss_q)
);
dffr #(
.DATA_WIDTH(8)
) u_sos_se_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_sos_se_d),
.dat_o (s_sos_se_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
dffr #(
.DATA_WIDTH(5)
) u_err_code_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_code_d),
.dat_o (s_err_code_q)
);
endmodule
+397
View File
@@ -0,0 +1,397 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_mcu_builder #(
parameter int unsigned DataWidth = 64,
parameter int unsigned MaxMcuBytes = 768
) (
// verilog_format: off -- preserve command, raster-stream, and block columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic [ 1:0] sampling_i,
input logic [ 2:0] format_i,
input logic [ 4:0] valid_width_i,
input logic [ 4:0] valid_height_i,
axi4_stream_if.sink pixel_axis,
output logic start_ready_o,
output logic [511:0] block_o,
output logic block_valid_o,
input logic block_ready_i,
output logic block_last_o,
output logic done_o,
output logic error_o
// verilog_format: on
);
localparam int unsigned InputBytes = DataWidth / 8;
localparam int unsigned ByteCountWidth = $clog2(MaxMcuBytes + 1);
typedef enum logic [2:0] {
Idle,
Capture,
Blocks,
Done
} state_e;
state_e s_state_d;
state_e s_state_q;
logic [ 2:0] s_state_bits_q;
logic [ 1:0] s_sampling_d;
logic [ 1:0] s_sampling_q;
logic [ 2:0] s_format_d;
logic [ 2:0] s_format_q;
logic [ 4:0] s_width_d;
logic [ 4:0] s_width_q;
logic [ 4:0] s_height_d;
logic [ 4:0] s_height_q;
logic [ByteCountWidth-1:0] s_byte_cnt_d;
logic [ByteCountWidth-1:0] s_byte_cnt_q;
logic [ByteCountWidth-1:0] s_expected_bytes_d;
logic [ByteCountWidth-1:0] s_expected_bytes_q;
logic [ 2:0] s_block_cnt_d;
logic [ 2:0] s_block_cnt_q;
logic [ MaxMcuBytes*8-1:0] s_raw_d;
logic [ MaxMcuBytes*8-1:0] s_raw_q;
logic s_err_d;
logic s_err_q;
logic [ 2:0] s_expected_blocks;
logic [ 511:0] s_generated_block;
logic [ ByteCountWidth:0] s_bytes_after_beat;
logic [ 3:0] s_input_byte_cnt;
logic s_keep_legal;
function automatic logic [7:0] raw_byte(input logic [MaxMcuBytes*8-1:0] raw_i,
input integer index_i);
if ((index_i < 0) || (index_i >= MaxMcuBytes)) begin
return 8'd0;
end
return raw_i[index_i*8+:8];
endfunction
function automatic logic [23:0] pixel_ycbcr(
input logic [MaxMcuBytes*8-1:0] raw_i, input logic [2:0] format_i, input integer width_i,
input integer height_i, input integer x_i, input integer y_i);
integer s_x;
integer s_y;
integer s_index;
integer s_pair;
integer s_uv_stride;
integer s_uv_base;
logic [ 7:0] s_red;
logic [ 7:0] s_green;
logic [ 7:0] s_blue;
logic [ 7:0] s_luma;
logic [ 7:0] s_cb;
logic [ 7:0] s_cr;
logic [15:0] s_rgb565;
begin
s_x = (x_i >= width_i) ? width_i - 1 : x_i;
s_y = (y_i >= height_i) ? height_i - 1 : y_i;
s_red = 8'd0;
s_green = 8'd0;
s_blue = 8'd0;
s_luma = 8'd0;
s_cb = 8'd128;
s_cr = 8'd128;
unique case (format_i)
3'd0: begin
s_luma = raw_byte(raw_i, (s_y * width_i) + s_x);
end
3'd1: begin
s_index = ((s_y * width_i) + s_x) * 2;
s_rgb565 = {raw_byte(raw_i, s_index + 1), raw_byte(raw_i, s_index)};
s_red = {s_rgb565[15:11], s_rgb565[15:13]};
s_green = {s_rgb565[10:5], s_rgb565[10:9]};
s_blue = {s_rgb565[4:0], s_rgb565[4:2]};
s_luma = (77 * s_red + 150 * s_green + 29 * s_blue + 128) >> 8;
s_cb = ((-43 * s_red - 85 * s_green + 128 * s_blue + 128) >>> 8) + 128;
s_cr = ((128 * s_red - 107 * s_green - 21 * s_blue + 128) >>> 8) + 128;
end
3'd2: begin
s_index = ((s_y * width_i) + s_x) * 3;
s_red = raw_byte(raw_i, s_index);
s_green = raw_byte(raw_i, s_index + 1);
s_blue = raw_byte(raw_i, s_index + 2);
s_luma = (77 * s_red + 150 * s_green + 29 * s_blue + 128) >> 8;
s_cb = ((-43 * s_red - 85 * s_green + 128 * s_blue + 128) >>> 8) + 128;
s_cr = ((128 * s_red - 107 * s_green - 21 * s_blue + 128) >>> 8) + 128;
end
3'd3: begin
s_pair = ((s_y * width_i) + (s_x & ~1)) * 2;
s_luma = raw_byte(raw_i, s_pair + ((s_x & 1) * 2));
s_cb = raw_byte(raw_i, s_pair + 1);
s_cr = raw_byte(raw_i, s_pair + 3);
end
default: begin
s_luma = raw_byte(raw_i, (s_y * width_i) + s_x);
s_uv_stride = (width_i + 1) & ~1;
s_uv_base = width_i * height_i;
s_pair = s_uv_base + ((s_y >> 1) * s_uv_stride) + ((s_x >> 1) * 2);
s_cb = raw_byte(raw_i, s_pair);
s_cr = raw_byte(raw_i, s_pair + 1);
end
endcase
return {s_cr, s_cb, s_luma};
end
endfunction
function automatic logic [7:0] component_sample(
input logic [MaxMcuBytes*8-1:0] raw_i, input logic [1:0] sampling_i,
input logic [2:0] format_i, input integer width_i, input integer height_i,
input integer block_i, input integer sample_i);
integer s_x;
integer s_y;
integer s_component;
logic [23:0] s_pixel0;
logic [23:0] s_pixel1;
logic [23:0] s_pixel2;
logic [23:0] s_pixel3;
logic [ 9:0] s_sum;
begin
s_x = sample_i & 7;
s_y = sample_i >> 3;
s_component = 0;
if (sampling_i == 2'd0) begin
s_component = 0;
end else if (sampling_i == 2'd1) begin
s_component = block_i;
end else if (sampling_i == 2'd2) begin
if (block_i < 2) begin
s_x += block_i * 8;
end else begin
s_x *= 2;
s_component = block_i - 1;
end
end else if (block_i < 4) begin
s_x += (block_i & 1) * 8;
s_y += (block_i >> 1) * 8;
end else begin
s_x *= 2;
s_y *= 2;
s_component = block_i - 3;
end
s_pixel0 = pixel_ycbcr(raw_i, format_i, width_i, height_i, s_x, s_y);
if ((sampling_i == 2'd2) && (block_i >= 2)) begin
s_pixel1 = pixel_ycbcr(raw_i, format_i, width_i, height_i, s_x + 1, s_y);
s_sum = 10'((s_component == 1) ? s_pixel0[15:8] : s_pixel0[23:16]) +
10'((s_component == 1) ? s_pixel1[15:8] : s_pixel1[23:16]);
return 8'((s_sum + 1'b1) >> 1);
end
if ((sampling_i == 2'd3) && (block_i >= 4)) begin
s_pixel1 = pixel_ycbcr(raw_i, format_i, width_i, height_i, s_x + 1, s_y);
s_pixel2 = pixel_ycbcr(raw_i, format_i, width_i, height_i, s_x, s_y + 1);
s_pixel3 = pixel_ycbcr(raw_i, format_i, width_i, height_i, s_x + 1, s_y + 1);
s_sum = 10'((s_component == 1) ? s_pixel0[15:8] : s_pixel0[23:16]) +
10'((s_component == 1) ? s_pixel1[15:8] : s_pixel1[23:16]) +
10'((s_component == 1) ? s_pixel2[15:8] : s_pixel2[23:16]) +
10'((s_component == 1) ? s_pixel3[15:8] : s_pixel3[23:16]);
return 8'((s_sum + 10'd2) >> 2);
end
unique case (s_component)
0: return s_pixel0[7:0];
1: return s_pixel0[15:8];
default: return s_pixel0[23:16];
endcase
end
endfunction
always_comb begin
unique case (s_sampling_q)
2'd0: s_expected_blocks = 3'd1;
2'd1: s_expected_blocks = 3'd3;
2'd2: s_expected_blocks = 3'd4;
default: s_expected_blocks = 3'd6;
endcase
for (int unsigned index = 0; index < 64; index++) begin
s_generated_block[index*8+:8] = component_sample(
s_raw_q,
s_sampling_q,
s_format_q,
int'(s_width_q),
int'(s_height_q),
int'(s_block_cnt_q),
index
);
end
end
always_comb begin
s_input_byte_cnt = 4'd0;
s_keep_legal = (pixel_axis.tkeep != '0) && (pixel_axis.tkeep == pixel_axis.tstrb);
for (int unsigned lane = 0; lane < InputBytes; lane++) begin
if (pixel_axis.tkeep[lane]) begin
s_input_byte_cnt += 1'b1;
end else if ((lane < (InputBytes - 1)) && pixel_axis.tkeep[lane+1]) begin
s_keep_legal = 1'b0;
end
end
s_bytes_after_beat = {1'b0, s_byte_cnt_q} + (ByteCountWidth + 1)'(s_input_byte_cnt);
end
assign s_state_q = state_e'(s_state_bits_q);
assign pixel_axis.tready = s_state_q == Capture;
assign start_ready_o = s_state_q == Idle;
assign block_o = s_generated_block;
assign block_valid_o = s_state_q == Blocks;
assign block_last_o = (s_state_q == Blocks) && (s_block_cnt_q + 1'b1 == s_expected_blocks);
assign done_o = s_state_q == Done;
assign error_o = s_err_q;
always_comb begin
s_state_d = s_state_q;
s_sampling_d = s_sampling_q;
s_format_d = s_format_q;
s_width_d = s_width_q;
s_height_d = s_height_q;
s_byte_cnt_d = s_byte_cnt_q;
s_expected_bytes_d = s_expected_bytes_q;
s_block_cnt_d = s_block_cnt_q;
s_raw_d = s_raw_q;
s_err_d = s_err_q;
unique case (s_state_q)
Idle: begin
if (start_i) begin
s_sampling_d = sampling_i;
s_format_d = format_i;
s_width_d = valid_width_i;
s_height_d = valid_height_i;
s_byte_cnt_d = '0;
s_block_cnt_d = '0;
s_raw_d = '0;
unique case (format_i)
3'd0: s_expected_bytes_d = ByteCountWidth'(int'(valid_width_i) * int'(valid_height_i));
3'd1, 3'd3:
s_expected_bytes_d = ByteCountWidth'(int'(valid_width_i) * int'(valid_height_i) * 2);
3'd2:
s_expected_bytes_d = ByteCountWidth'(int'(valid_width_i) * int'(valid_height_i) * 3);
default:
s_expected_bytes_d = ByteCountWidth'(
(int'(valid_width_i) * int'(valid_height_i)) +
(((int'(valid_width_i) + 1) & ~1) * ((int'(valid_height_i) + 1) >> 1))
);
endcase
s_err_d = (format_i > 3'd4) || (valid_width_i == 5'd0) ||
(valid_height_i == 5'd0) || (valid_width_i > 5'd16) ||
(valid_height_i > 5'd16);
s_state_d = Capture;
end
end
Capture: begin
if (pixel_axis.tvalid && pixel_axis.tready) begin
if (!s_keep_legal || (s_bytes_after_beat > {1'b0, s_expected_bytes_q})) begin
s_err_d = 1'b1;
end
for (int unsigned lane = 0; lane < InputBytes; lane++) begin
if (pixel_axis.tkeep[lane] && ((int'(s_byte_cnt_q) + lane) < MaxMcuBytes)) begin
s_raw_d[(int'(s_byte_cnt_q)+lane)*8+:8] = pixel_axis.tdata[lane*8+:8];
end
end
s_byte_cnt_d = ByteCountWidth'(s_bytes_after_beat);
if (s_bytes_after_beat >= {1'b0, s_expected_bytes_q}) begin
s_block_cnt_d = 3'd0;
s_state_d = Blocks;
end
end
end
Blocks: begin
if (block_valid_o && block_ready_i) begin
if (block_last_o) begin
s_state_d = Done;
end else begin
s_block_cnt_d = s_block_cnt_q + 1'b1;
end
end
end
Done: s_state_d = Idle;
default: s_state_d = Idle;
endcase
end
dffr #(
.DATA_WIDTH(3)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(2)
) u_sampling_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_sampling_d),
.dat_o (s_sampling_q)
);
dffr #(
.DATA_WIDTH(3)
) u_format_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_format_d),
.dat_o (s_format_q)
);
dffr #(
.DATA_WIDTH(5)
) u_width_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_width_d),
.dat_o (s_width_q)
);
dffr #(
.DATA_WIDTH(5)
) u_height_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_height_d),
.dat_o (s_height_q)
);
dffr #(
.DATA_WIDTH(ByteCountWidth)
) u_byte_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_byte_cnt_d),
.dat_o (s_byte_cnt_q)
);
dffr #(
.DATA_WIDTH(ByteCountWidth)
) u_expected_bytes_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_expected_bytes_d),
.dat_o (s_expected_bytes_q)
);
dffr #(
.DATA_WIDTH(3)
) u_block_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_block_cnt_d),
.dat_o (s_block_cnt_q)
);
dffr #(
.DATA_WIDTH(MaxMcuBytes * 8)
) u_raw_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_raw_d),
.dat_o (s_raw_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
`ifndef SYNTHESIS
initial begin
if (DataWidth != 64 || MaxMcuBytes < 768) begin
$fatal(1, "jpeg_mcu_builder: unsupported buffer geometry");
end
end
`endif
endmodule
+380
View File
@@ -0,0 +1,380 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_mcu_reconstructor (
// verilog_format: off -- preserve command, block, and raster-stream interface columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic [ 1:0] sampling_i,
input logic [ 2:0] format_i,
input logic [ 4:0] valid_width_i,
input logic [ 4:0] valid_height_i,
input logic [511:0] block_i,
input logic block_valid_i,
output logic block_ready_o,
output logic start_ready_o,
axi4_stream_if.source pixel_axis,
output logic done_o,
output logic error_o
// verilog_format: on
);
typedef enum logic [2:0] {
Idle,
Load,
Pixels,
Nv12Chroma,
Done
} state_e;
state_e s_state_d;
state_e s_state_q;
logic [ 2:0] s_state_bits_q;
logic [ 1:0] s_sampling_d;
logic [ 1:0] s_sampling_q;
logic [ 2:0] s_format_d;
logic [ 2:0] s_format_q;
logic [ 4:0] s_width_d;
logic [ 4:0] s_width_q;
logic [ 4:0] s_height_d;
logic [ 4:0] s_height_q;
logic [ 2:0] s_block_cnt_d;
logic [ 2:0] s_block_cnt_q;
logic [ 5:0][511:0] s_blocks_d;
logic [ 5:0][511:0] s_blocks_q;
logic [ 4:0] s_x_d;
logic [ 4:0] s_x_q;
logic [ 4:0] s_y_d;
logic [ 4:0] s_y_q;
logic s_err_d;
logic s_err_q;
logic [ 2:0] s_expected_blocks;
logic [ 7:0] s_y0;
logic [ 7:0] s_y1;
logic [ 7:0] s_cb0;
logic [ 7:0] s_cb1;
logic [ 7:0] s_cr0;
logic [ 7:0] s_cr1;
logic [ 7:0] s_red0;
logic [ 7:0] s_green0;
logic [ 7:0] s_blue0;
logic [ 7:0] s_red1;
logic [ 7:0] s_green1;
logic [ 7:0] s_blue1;
logic [15:0] s_rgb565_0;
logic [15:0] s_rgb565_1;
logic s_second_valid;
logic s_pixel_accept;
logic [ 4:0] s_x_next;
function automatic logic [7:0] block_sample(input logic [511:0] block_i, input logic [2:0] x_i,
input logic [2:0] y_i);
return block_i[((y_i*8)+x_i)*8+:8];
endfunction
function automatic logic [7:0] clamp_color(input logic signed [17:0] value_i);
if (value_i < 0) begin
return 8'd0;
end
if (value_i > 255) begin
return 8'd255;
end
return value_i[7:0];
endfunction
function automatic logic [23:0] ycbcr_to_rgb(input logic [7:0] y_i, input logic [7:0] cb_i,
input logic [7:0] cr_i);
logic signed [ 9:0] s_cb;
logic signed [ 9:0] s_cr;
logic signed [17:0] s_red;
logic signed [17:0] s_green;
logic signed [17:0] s_blue;
begin
s_cb = $signed({1'b0, cb_i}) - 10'sd128;
s_cr = $signed({1'b0, cr_i}) - 10'sd128;
s_red = $signed(18'({1'b0, y_i})) + ((18'sd359 * s_cr + 18'sd128) >>> 8);
s_green = $signed(18'({1'b0, y_i})) -
((18'sd88 * s_cb + 18'sd183 * s_cr + 18'sd128) >>> 8);
s_blue = $signed(18'({1'b0, y_i})) + ((18'sd454 * s_cb + 18'sd128) >>> 8);
return {clamp_color(s_red), clamp_color(s_green), clamp_color(s_blue)};
end
endfunction
function automatic logic [7:0] average_chroma(input logic [7:0] left_i,
input logic [7:0] right_i);
logic [8:0] s_sum;
begin
s_sum = {1'b0, left_i} + {1'b0, right_i} + 1'b1;
return s_sum[8:1];
end
endfunction
always_comb begin
unique case (s_sampling_q)
2'd0: s_expected_blocks = 3'd1;
2'd1: s_expected_blocks = 3'd3;
2'd2: s_expected_blocks = 3'd4;
default: s_expected_blocks = 3'd6;
endcase
end
always_comb begin
s_y0 = 8'd0;
s_y1 = 8'd0;
s_cb0 = 8'd128;
s_cb1 = 8'd128;
s_cr0 = 8'd128;
s_cr1 = 8'd128;
unique case (s_sampling_q)
2'd0: begin
s_y0 = block_sample(s_blocks_q[0], s_x_q[2:0], s_y_q[2:0]);
s_y1 = block_sample(s_blocks_q[0], s_x_next[2:0], s_y_q[2:0]);
end
2'd1: begin
s_y0 = block_sample(s_blocks_q[0], s_x_q[2:0], s_y_q[2:0]);
s_y1 = block_sample(s_blocks_q[0], s_x_next[2:0], s_y_q[2:0]);
s_cb0 = block_sample(s_blocks_q[1], s_x_q[2:0], s_y_q[2:0]);
s_cb1 = block_sample(s_blocks_q[1], s_x_next[2:0], s_y_q[2:0]);
s_cr0 = block_sample(s_blocks_q[2], s_x_q[2:0], s_y_q[2:0]);
s_cr1 = block_sample(s_blocks_q[2], s_x_next[2:0], s_y_q[2:0]);
end
2'd2: begin
s_y0 = block_sample(s_blocks_q[s_x_q[3]], s_x_q[2:0], s_y_q[2:0]);
s_y1 = block_sample(s_blocks_q[s_x_next[3]], s_x_next[2:0], s_y_q[2:0]);
s_cb0 = block_sample(s_blocks_q[2], s_x_q[3:1], s_y_q[2:0]);
s_cb1 = block_sample(s_blocks_q[2], s_x_next[3:1], s_y_q[2:0]);
s_cr0 = block_sample(s_blocks_q[3], s_x_q[3:1], s_y_q[2:0]);
s_cr1 = block_sample(s_blocks_q[3], s_x_next[3:1], s_y_q[2:0]);
end
default: begin
s_y0 = block_sample(s_blocks_q[{s_y_q[3], s_x_q[3]}], s_x_q[2:0], s_y_q[2:0]);
s_y1 = block_sample(s_blocks_q[{s_y_q[3], s_x_next[3]}], s_x_next[2:0], s_y_q[2:0]);
s_cb0 = block_sample(s_blocks_q[4], s_x_q[3:1], s_y_q[3:1]);
s_cb1 = block_sample(s_blocks_q[4], s_x_next[3:1], s_y_q[3:1]);
s_cr0 = block_sample(s_blocks_q[5], s_x_q[3:1], s_y_q[3:1]);
s_cr1 = block_sample(s_blocks_q[5], s_x_next[3:1], s_y_q[3:1]);
end
endcase
end
assign {s_red0, s_green0, s_blue0} = ycbcr_to_rgb(s_y0, s_cb0, s_cr0);
assign {s_red1, s_green1, s_blue1} = ycbcr_to_rgb(s_y1, s_cb1, s_cr1);
assign s_rgb565_0 = {s_red0[7:3], s_green0[7:2], s_blue0[7:3]};
assign s_rgb565_1 = {s_red1[7:3], s_green1[7:2], s_blue1[7:3]};
assign s_x_next = s_x_q + 1'b1;
assign s_second_valid = (s_x_q + 1'b1) < s_width_q;
assign s_pixel_accept = pixel_axis.tvalid && pixel_axis.tready;
assign s_state_q = state_e'(s_state_bits_q);
assign start_ready_o = s_state_q == Idle;
assign block_ready_o = s_state_q == Load;
assign done_o = s_state_q == Done;
assign error_o = s_err_q;
always_comb begin
pixel_axis.tdata = '0;
pixel_axis.tkeep = '0;
pixel_axis.tstrb = '0;
pixel_axis.tlast = 1'b0;
pixel_axis.tid = '0;
pixel_axis.tdest = '0;
pixel_axis.tuser = '0;
pixel_axis.tvalid = (s_state_q == Pixels) || (s_state_q == Nv12Chroma);
if (s_state_q == Nv12Chroma) begin
pixel_axis.tdata[7:0] = s_cb0;
pixel_axis.tdata[15:8] = s_cr0;
pixel_axis.tkeep = 8'h03;
pixel_axis.tstrb = 8'h03;
pixel_axis.tlast = (s_x_q + 2 >= s_width_q);
pixel_axis.tdest = 1'b1;
pixel_axis.tuser = (s_x_q == 5'd0) && (s_y_q == 5'd0);
end else begin
unique case (s_format_q)
3'd0: begin
pixel_axis.tdata[7:0] = s_y0;
pixel_axis.tdata[15:8] = s_y1;
pixel_axis.tkeep = s_second_valid ? 8'h03 : 8'h01;
end
3'd1: begin
pixel_axis.tdata[15:0] = s_rgb565_0;
pixel_axis.tdata[31:16] = s_rgb565_1;
pixel_axis.tkeep = s_second_valid ? 8'h0f : 8'h03;
end
3'd2: begin
pixel_axis.tdata[23:0] = {s_blue0, s_green0, s_red0};
pixel_axis.tdata[47:24] = {s_blue1, s_green1, s_red1};
pixel_axis.tkeep = s_second_valid ? 8'h3f : 8'h07;
end
3'd3: begin
pixel_axis.tdata[31:0] = {
average_chroma(s_cr0, s_cr1), s_y1, average_chroma(s_cb0, s_cb1), s_y0
};
pixel_axis.tkeep = 8'h0f;
end
default: begin
pixel_axis.tdata[7:0] = s_y0;
pixel_axis.tdata[15:8] = s_y1;
pixel_axis.tkeep = s_second_valid ? 8'h03 : 8'h01;
end
endcase
pixel_axis.tstrb = pixel_axis.tkeep;
pixel_axis.tlast = (s_x_q + 2 >= s_width_q);
pixel_axis.tuser = (s_x_q == 5'd0) && (s_y_q == 5'd0);
end
end
always_comb begin
s_state_d = s_state_q;
s_sampling_d = s_sampling_q;
s_format_d = s_format_q;
s_width_d = s_width_q;
s_height_d = s_height_q;
s_block_cnt_d = s_block_cnt_q;
s_blocks_d = s_blocks_q;
s_x_d = s_x_q;
s_y_d = s_y_q;
s_err_d = s_err_q;
unique case (s_state_q)
Idle: begin
if (start_i) begin
s_sampling_d = sampling_i;
s_format_d = format_i;
s_width_d = valid_width_i;
s_height_d = valid_height_i;
s_block_cnt_d = 3'd0;
s_blocks_d = '0;
s_x_d = 5'd0;
s_y_d = 5'd0;
s_err_d = (format_i > 3'd4) || (valid_width_i == 5'd0) ||
(valid_height_i == 5'd0) || (valid_width_i > 5'd16) ||
(valid_height_i > 5'd16);
s_state_d = Load;
end
end
Load: begin
if (block_valid_i) begin
s_blocks_d[s_block_cnt_q] = block_i;
if (s_block_cnt_q + 1'b1 == s_expected_blocks) begin
s_x_d = 5'd0;
s_y_d = 5'd0;
s_state_d = Pixels;
end else begin
s_block_cnt_d = s_block_cnt_q + 1'b1;
end
end
end
Pixels: begin
if (s_pixel_accept) begin
if (s_x_q + 2 >= s_width_q) begin
s_x_d = 5'd0;
if (s_y_q + 1'b1 >= s_height_q) begin
s_y_d = 5'd0;
s_state_d = (s_format_q == 3'd4 && s_sampling_q != 2'd0) ? Nv12Chroma : Done;
end else begin
s_y_d = s_y_q + 1'b1;
end
end else begin
s_x_d = s_x_q + 2;
end
end
end
Nv12Chroma: begin
if (s_pixel_accept) begin
if (s_x_q + 2 >= s_width_q) begin
s_x_d = 5'd0;
if (s_y_q + 2 >= s_height_q) begin
s_state_d = Done;
end else begin
s_y_d = s_y_q + 2;
end
end else begin
s_x_d = s_x_q + 2;
end
end
end
Done: begin
s_state_d = Idle;
end
default: s_state_d = Idle;
endcase
end
dffr #(
.DATA_WIDTH(3)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(2)
) u_sampling_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_sampling_d),
.dat_o (s_sampling_q)
);
dffr #(
.DATA_WIDTH(3)
) u_format_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_format_d),
.dat_o (s_format_q)
);
dffr #(
.DATA_WIDTH(5)
) u_width_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_width_d),
.dat_o (s_width_q)
);
dffr #(
.DATA_WIDTH(5)
) u_height_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_height_d),
.dat_o (s_height_q)
);
dffr #(
.DATA_WIDTH(3)
) u_block_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_block_cnt_d),
.dat_o (s_block_cnt_q)
);
dffr #(
.DATA_WIDTH(6 * 512)
) u_blocks_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_blocks_d),
.dat_o (s_blocks_q)
);
dffr #(
.DATA_WIDTH(5)
) u_x_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_x_d),
.dat_o (s_x_q)
);
dffr #(
.DATA_WIDTH(5)
) u_y_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_y_d),
.dat_o (s_y_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
endmodule
+93
View File
@@ -0,0 +1,93 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
package jpeg_pkg;
typedef enum logic {
JpegEncode,
JpegDecode
} jpeg_mode_e;
typedef enum logic [2:0] {
JpegGray8,
JpegRgb565,
JpegRgb888,
JpegYuyv422,
JpegNv12
} jpeg_pixel_format_e;
typedef enum logic [1:0] {
JpegSamplingGray,
JpegSampling444,
JpegSampling422,
JpegSampling420
} jpeg_sampling_e;
typedef enum logic [4:0] {
JpegErrorNone,
JpegErrorConfig,
JpegErrorDescriptor,
JpegErrorTable,
JpegErrorUnsupportedMarker,
JpegErrorUnsupportedProcess,
JpegErrorUnsupportedPrecision,
JpegErrorUnsupportedSampling,
JpegErrorMalformedMarker,
JpegErrorMalformedHuffman,
JpegErrorTruncated,
JpegErrorRestart,
JpegErrorOutputOverflow,
JpegErrorAxiRead,
JpegErrorAxiWrite,
JpegErrorAxiDescriptor,
JpegErrorTimeout,
JpegErrorAborted,
JpegErrorInternal
} jpeg_error_e;
typedef enum logic [3:0] {
JpegStageIdle,
JpegStageDescriptor,
JpegStageHeader,
JpegStageInputDma,
JpegStageColor,
JpegStageTransform,
JpegStageQuantize,
JpegStageEntropy,
JpegStageOutputDma,
JpegStageWriteback
} jpeg_stage_e;
typedef struct packed {
logic [31:0] control;
logic [31:0] status;
logic [31:0] image_size;
logic [31:0] encode_config;
logic [31:0] restart_interval;
logic [31:0] bitstream_addr;
logic [31:0] bitstream_size;
logic [31:0] result_size;
logic [31:0] plane0_addr;
logic [31:0] plane0_stride;
logic [31:0] plane1_addr;
logic [31:0] plane1_stride;
logic [31:0] plane2_addr;
logic [31:0] plane2_stride;
logic [31:0] metadata_addr;
logic [31:0] metadata_length;
logic [31:0] cookie_lo;
logic [31:0] cookie_hi;
logic [31:0] result_image_size;
logic [31:0] result_format;
logic [31:0] cycles_lo;
logic [31:0] cycles_hi;
logic [31:0] input_bytes;
logic [31:0] output_bytes;
logic [255:0] reserved;
} jpeg_descriptor_t;
function automatic logic pixel_format_valid(input logic [2:0] format_i);
return format_i <= JpegNv12;
endfunction
endpackage
+246
View File
@@ -0,0 +1,246 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_quantizer #(
parameter int unsigned ElementWidth = 24,
parameter int unsigned ReciprocalWidth = 25,
parameter int unsigned ReciprocalShift = 24
) (
// verilog_format: off -- preserve the block and table interface columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic dequantize_i,
input logic [64*ElementWidth-1:0] block_i,
input logic [64*8-1:0] quant_i,
input logic [64*ReciprocalWidth-1:0] reciprocal_i,
output logic start_ready_o,
output logic [64*ElementWidth-1:0] block_o,
output logic result_valid_o,
input logic result_ready_i,
output logic table_err_o,
output logic overflow_o
// verilog_format: on
);
typedef enum logic [1:0] {
Idle,
Process,
Result
} state_e;
state_e s_state_d;
state_e s_state_q;
logic [ 1:0] s_state_bits_q;
logic [ 2:0] s_group_d;
logic [ 2:0] s_group_q;
logic s_dequantize_d;
logic s_dequantize_q;
logic [ 64*ElementWidth-1:0] s_input_d;
logic [ 64*ElementWidth-1:0] s_input_q;
logic [ 64*8-1:0] s_quant_d;
logic [ 64*8-1:0] s_quant_q;
logic [64*ReciprocalWidth-1:0] s_reciprocal_d;
logic [64*ReciprocalWidth-1:0] s_reciprocal_q;
logic [ 64*ElementWidth-1:0] s_result_d;
logic [ 64*ElementWidth-1:0] s_result_q;
logic s_table_err_d;
logic s_table_err_q;
logic s_overflow_d;
logic s_overflow_q;
function automatic logic signed [ElementWidth-1:0] quantize_value(
input logic signed [ElementWidth-1:0] value_i, input logic [7:0] quant_i,
input logic [ReciprocalWidth-1:0] reciprocal_i);
logic [ ElementWidth:0] s_magnitude;
logic [ ElementWidth:0] s_numerator;
logic [ElementWidth+ReciprocalWidth:0] s_scaled;
logic [ ElementWidth:0] s_estimate;
logic [ ElementWidth+8:0] s_product;
begin
if (value_i < 0) begin
s_magnitude = {1'b0, ElementWidth'(-value_i)};
end else begin
s_magnitude = {1'b0, value_i};
end
s_numerator = s_magnitude + (ElementWidth + 1)'({1'b0, quant_i[7:1]});
s_scaled = s_numerator * reciprocal_i;
s_estimate = (ElementWidth + 1)'(s_scaled >> ReciprocalShift);
s_product = s_estimate * quant_i;
if (s_product > (ElementWidth + 9)'(s_numerator)) begin
s_estimate -= 1'b1;
end else if (((s_estimate + 1'b1) * quant_i) <= s_numerator) begin
s_estimate += 1'b1;
end
quantize_value = (value_i < 0) ? -ElementWidth'(s_estimate) : ElementWidth'(s_estimate);
end
endfunction
function automatic logic signed [ElementWidth-1:0] dequantize_value(
input logic signed [ElementWidth-1:0] value_i, input logic [7:0] quant_i);
logic signed [ElementWidth+8-1:0] s_product;
begin
s_product = value_i * $signed({1'b0, quant_i});
return ElementWidth'(s_product);
end
endfunction
function automatic logic product_overflows(input logic signed [ElementWidth-1:0] value_i,
input logic [7:0] quant_i);
logic signed [ElementWidth+8-1:0] s_product;
logic signed [ ElementWidth-1:0] s_truncated;
begin
s_product = value_i * $signed({1'b0, quant_i});
s_truncated = ElementWidth'(s_product);
return s_product != {{8{s_truncated[ElementWidth-1]}}, s_truncated};
end
endfunction
assign s_state_q = state_e'(s_state_bits_q);
assign start_ready_o = s_state_q == Idle;
assign block_o = s_result_q;
assign result_valid_o = s_state_q == Result;
assign table_err_o = s_table_err_q;
assign overflow_o = s_overflow_q;
always_comb begin
s_state_d = s_state_q;
s_group_d = s_group_q;
s_dequantize_d = s_dequantize_q;
s_input_d = s_input_q;
s_quant_d = s_quant_q;
s_reciprocal_d = s_reciprocal_q;
s_result_d = s_result_q;
s_table_err_d = s_table_err_q;
s_overflow_d = s_overflow_q;
unique case (s_state_q)
Idle: begin
s_group_d = 3'd0;
if (start_i) begin
s_dequantize_d = dequantize_i;
s_input_d = block_i;
s_quant_d = quant_i;
s_reciprocal_d = reciprocal_i;
s_result_d = '0;
s_table_err_d = 1'b0;
s_overflow_d = 1'b0;
s_state_d = Process;
end
end
Process: begin
for (int unsigned lane = 0; lane < 8; lane++) begin
if (s_quant_q[((s_group_q*8)+lane)*8+:8] == 8'd0) begin
s_table_err_d = 1'b1;
end else if (s_dequantize_q) begin
s_result_d[((s_group_q*8)+lane)*ElementWidth+:ElementWidth] = dequantize_value(
s_input_q[((s_group_q*8)+lane)*ElementWidth+:ElementWidth],
s_quant_q[((s_group_q*8)+lane)*8+:8]
);
if (product_overflows(
s_input_q[((s_group_q*8)+lane)*ElementWidth+:ElementWidth],
s_quant_q[((s_group_q*8)+lane)*8+:8]
)) begin
s_overflow_d = 1'b1;
end
end else begin
s_result_d[((s_group_q*8)+lane)*ElementWidth+:ElementWidth] = quantize_value(
s_input_q[((s_group_q*8)+lane)*ElementWidth+:ElementWidth],
s_quant_q[((s_group_q*8)+lane)*8+:8],
s_reciprocal_q[((s_group_q*8)+lane)*ReciprocalWidth+:ReciprocalWidth]
);
end
end
if (s_group_q == 3'd7) begin
s_group_d = 3'd0;
s_state_d = Result;
end else begin
s_group_d = s_group_q + 1'b1;
end
end
Result: begin
if (result_ready_i) begin
s_state_d = Idle;
end
end
default: begin
s_state_d = Idle;
end
endcase
end
dffr #(
.DATA_WIDTH(2)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(3)
) u_group_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_group_d),
.dat_o (s_group_q)
);
dffr u_dequantize_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dequantize_d),
.dat_o (s_dequantize_q)
);
dffr #(
.DATA_WIDTH(64 * ElementWidth)
) u_input_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_input_d),
.dat_o (s_input_q)
);
dffr #(
.DATA_WIDTH(64 * 8)
) u_quant_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_quant_d),
.dat_o (s_quant_q)
);
dffr #(
.DATA_WIDTH(64 * ReciprocalWidth)
) u_reciprocal_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_reciprocal_d),
.dat_o (s_reciprocal_q)
);
dffr #(
.DATA_WIDTH(64 * ElementWidth)
) u_result_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_result_d),
.dat_o (s_result_q)
);
dffr u_table_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_table_err_d),
.dat_o (s_table_err_q)
);
dffr u_overflow_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_overflow_d),
.dat_o (s_overflow_q)
);
`ifndef SYNTHESIS
initial begin
if (ElementWidth < 16 || ReciprocalWidth <= ReciprocalShift) begin
$fatal(1, "jpeg_quantizer: invalid arithmetic widths");
end
end
`endif
endmodule
+491
View File
@@ -0,0 +1,491 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
`include "jpeg_define.svh"
module jpeg_reg (
// verilog_format: off -- preserve APB, engine, ring, and table interface columns
input logic clk_i,
input logic rst_n_i,
apb4_if.slave apb4,
input logic busy_i,
input logic ring_active_i,
input logic encode_i,
input logic quiesce_i,
input logic job_done_i,
input logic ring_event_i,
input logic header_ready_i,
input logic abort_done_i,
input logic error_event_i,
input logic [ 4:0] error_code_i,
input logic [ 3:0] error_stage_i,
input logic [ 1:0] error_axi_resp_i,
input logic [31:0] error_addr_i,
input logic [31:0] error_detail_i,
input logic [63:0] cycles_i,
input logic [31:0] pixels_i,
input logic [31:0] input_bytes_i,
input logic [31:0] output_bytes_i,
input logic [31:0] read_stall_i,
input logic [31:0] write_stall_i,
input logic [31:0] result_size_i,
input logic [31:0] result_image_size_i,
input logic [31:0] result_format_i,
input logic [31:0] result_markers_i,
input logic [31:0] ring_head_i,
input logic [31:0] ring_status_i,
input logic [31:0] table_data_i,
input logic [31:0] table_status_i,
output logic start_o,
output logic abort_o,
output logic soft_reset_o,
output logic ring_kick_o,
output logic [31:0] job_config_o,
output logic [31:0] image_size_o,
output logic [31:0] input_format_o,
output logic [31:0] output_format_o,
output logic [31:0] encode_config_o,
output logic [31:0] restart_interval_o,
output logic [31:0] bitstream_addr_o,
output logic [31:0] bitstream_size_o,
output logic [31:0] plane0_addr_o,
output logic [31:0] plane0_stride_o,
output logic [31:0] plane1_addr_o,
output logic [31:0] plane1_stride_o,
output logic [31:0] plane2_addr_o,
output logic [31:0] plane2_stride_o,
output logic [31:0] metadata_addr_o,
output logic [31:0] metadata_length_o,
output logic [31:0] ring_base_o,
output logic [31:0] ring_size_o,
output logic [31:0] ring_tail_o,
output logic [31:0] ring_control_o,
output logic [31:0] irq_coalesce_o,
output logic [ 1:0] table_context_o,
output logic [ 3:0] table_kind_o,
output logic [ 7:0] table_index_o,
output logic [31:0] table_write_data_o,
output logic table_write_o,
output logic table_commit_o,
output logic table_default_o,
output logic table_clear_o,
output logic perf_enable_o,
output logic perf_clear_o,
output logic irq_o
// verilog_format: on
);
localparam logic [31:0] IpId = 32'h4a504547;
localparam logic [31:0] IpVersion = 32'h00010000;
localparam logic [31:0] Capability0 = 32'h001f0f3f;
localparam logic [31:0] Capability1 = 32'h08400800;
logic s_req;
logic s_write;
logic s_req_accept;
logic s_access_err;
logic [11:0] s_offset;
logic [31:0] s_read_data;
logic [31:0] s_write_value;
logic [ 4:0] s_irq_state_d;
logic [ 4:0] s_irq_state_q;
logic [ 4:0] s_irq_en_d;
logic [ 4:0] s_irq_en_q;
logic [ 4:0] s_irq_event;
logic [ 4:0] s_irq_clear;
logic [31:0] s_err_stat_d;
logic [31:0] s_err_stat_q;
logic [31:0] s_err_addr_d;
logic [31:0] s_err_addr_q;
logic [31:0] s_err_detail_d;
logic [31:0] s_err_detail_q;
logic [ 1:0] s_perf_control_d;
logic [ 1:0] s_perf_control_q;
logic [15:0][31:0] s_cfg_d;
logic [15:0][31:0] s_cfg_q;
logic [ 4:0][31:0] s_ring_d;
logic [ 4:0][31:0] s_ring_q;
logic [ 1:0] s_table_context_d;
logic [ 1:0] s_table_context_q;
logic [ 3:0] s_table_kind_d;
logic [ 3:0] s_table_kind_q;
logic [ 7:0] s_table_index_d;
logic [ 7:0] s_table_index_q;
function automatic logic [31:0] apply_wstrb(
input logic [31:0] previous_i, input logic [31:0] value_i, input logic [3:0] strobe_i);
logic [31:0] s_value;
begin
s_value = previous_i;
for (int unsigned byte_index = 0; byte_index < 4; byte_index++) begin
if (strobe_i[byte_index]) begin
s_value[byte_index*8+:8] = value_i[byte_index*8+:8];
end
end
return s_value;
end
endfunction
assign s_req = apb4.psel && apb4.penable;
assign s_write = s_req && apb4.pwrite;
assign s_req_accept = s_req;
assign s_offset = apb4.paddr[11:0];
assign s_write_value = apb4.pwdata;
assign apb4.pready = s_req_accept;
assign apb4.pslverr = s_access_err;
assign apb4.prdata = s_read_data;
assign s_irq_event = {error_event_i, abort_done_i, header_ready_i, ring_event_i, job_done_i};
assign irq_o = |(s_irq_state_q & s_irq_en_q);
assign perf_enable_o = s_perf_control_q[0];
assign job_config_o = s_cfg_q[0];
assign image_size_o = s_cfg_q[1];
assign input_format_o = s_cfg_q[2];
assign output_format_o = s_cfg_q[3];
assign encode_config_o = s_cfg_q[4];
assign restart_interval_o = s_cfg_q[5];
assign bitstream_addr_o = s_cfg_q[6];
assign bitstream_size_o = s_cfg_q[7];
assign plane0_addr_o = s_cfg_q[8];
assign plane0_stride_o = s_cfg_q[9];
assign plane1_addr_o = s_cfg_q[10];
assign plane1_stride_o = s_cfg_q[11];
assign plane2_addr_o = s_cfg_q[12];
assign plane2_stride_o = s_cfg_q[13];
assign metadata_addr_o = s_cfg_q[14];
assign metadata_length_o = s_cfg_q[15];
assign ring_base_o = s_ring_q[0];
assign ring_size_o = s_ring_q[1];
assign ring_tail_o = s_ring_q[2];
assign ring_control_o = s_ring_q[3];
assign irq_coalesce_o = s_ring_q[4];
assign table_context_o = s_table_context_q;
assign table_kind_o = s_table_kind_q;
assign table_index_o = s_table_index_q;
always_comb begin
s_read_data = 32'd0;
unique case (s_offset)
`APB4_JPEG__IP_ID: s_read_data = IpId;
`APB4_JPEG__IP_VERSION: s_read_data = IpVersion;
`APB4_JPEG__CAPABILITY0: s_read_data = Capability0;
`APB4_JPEG__CAPABILITY1: s_read_data = Capability1;
`APB4_JPEG__STATUS: s_read_data = {28'd0, !busy_i, encode_i, ring_active_i, busy_i};
`APB4_JPEG__IRQ_STATE: s_read_data = {27'd0, s_irq_state_q};
`APB4_JPEG__IRQ_ENABLE: s_read_data = {27'd0, s_irq_en_q};
`APB4_JPEG__ERROR_STATUS: s_read_data = s_err_stat_q;
`APB4_JPEG__ERROR_ADDRESS: s_read_data = s_err_addr_q;
`APB4_JPEG__ERROR_DETAIL: s_read_data = s_err_detail_q;
`APB4_JPEG__PERF_CONTROL: s_read_data = {30'd0, s_perf_control_q};
`APB4_JPEG__CYCLES_LO: s_read_data = cycles_i[31:0];
`APB4_JPEG__CYCLES_HI: s_read_data = cycles_i[63:32];
`APB4_JPEG__PIXELS: s_read_data = pixels_i;
`APB4_JPEG__INPUT_BYTES: s_read_data = input_bytes_i;
`APB4_JPEG__OUTPUT_BYTES: s_read_data = output_bytes_i;
`APB4_JPEG__READ_STALL: s_read_data = read_stall_i;
`APB4_JPEG__WRITE_STALL: s_read_data = write_stall_i;
`APB4_JPEG__JOB_CONFIG: s_read_data = s_cfg_q[0];
`APB4_JPEG__IMAGE_SIZE: s_read_data = s_cfg_q[1];
`APB4_JPEG__INPUT_FORMAT: s_read_data = s_cfg_q[2];
`APB4_JPEG__OUTPUT_FORMAT: s_read_data = s_cfg_q[3];
`APB4_JPEG__ENCODE_CONFIG: s_read_data = s_cfg_q[4];
`APB4_JPEG__RESTART_INTERVAL: s_read_data = s_cfg_q[5];
`APB4_JPEG__BITSTREAM_ADDR: s_read_data = s_cfg_q[6];
`APB4_JPEG__BITSTREAM_SIZE: s_read_data = s_cfg_q[7];
`APB4_JPEG__PLANE0_ADDR: s_read_data = s_cfg_q[8];
`APB4_JPEG__PLANE0_STRIDE: s_read_data = s_cfg_q[9];
`APB4_JPEG__PLANE1_ADDR: s_read_data = s_cfg_q[10];
`APB4_JPEG__PLANE1_STRIDE: s_read_data = s_cfg_q[11];
`APB4_JPEG__PLANE2_ADDR: s_read_data = s_cfg_q[12];
`APB4_JPEG__PLANE2_STRIDE: s_read_data = s_cfg_q[13];
`APB4_JPEG__METADATA_ADDR: s_read_data = s_cfg_q[14];
`APB4_JPEG__METADATA_LENGTH: s_read_data = s_cfg_q[15];
`APB4_JPEG__RESULT_SIZE: s_read_data = result_size_i;
`APB4_JPEG__RESULT_IMAGE_SIZE: s_read_data = result_image_size_i;
`APB4_JPEG__RESULT_FORMAT: s_read_data = result_format_i;
`APB4_JPEG__RESULT_MARKERS: s_read_data = result_markers_i;
`APB4_JPEG__RING_BASE: s_read_data = s_ring_q[0];
`APB4_JPEG__RING_SIZE: s_read_data = s_ring_q[1];
`APB4_JPEG__RING_HEAD: s_read_data = ring_head_i;
`APB4_JPEG__RING_TAIL: s_read_data = s_ring_q[2];
`APB4_JPEG__RING_CONTROL: s_read_data = s_ring_q[3];
`APB4_JPEG__RING_STATUS: s_read_data = ring_status_i;
`APB4_JPEG__IRQ_COALESCE: s_read_data = s_ring_q[4];
`APB4_JPEG__TABLE_CONTEXT: s_read_data = {30'd0, s_table_context_q};
`APB4_JPEG__TABLE_KIND: s_read_data = {28'd0, s_table_kind_q};
`APB4_JPEG__TABLE_INDEX: s_read_data = {24'd0, s_table_index_q};
`APB4_JPEG__TABLE_DATA: s_read_data = table_data_i;
`APB4_JPEG__TABLE_STATUS: s_read_data = table_status_i;
default: s_read_data = 32'd0;
endcase
end
always_comb begin
s_access_err = 1'b0;
if (s_req && (apb4.paddr[1:0] != 2'b00)) begin
s_access_err = 1'b1;
end
if (s_write) begin
unique case (s_offset)
`APB4_JPEG__COMMAND, `APB4_JPEG__IRQ_STATE, `APB4_JPEG__IRQ_ENABLE,
`APB4_JPEG__IRQ_TEST, `APB4_JPEG__ERROR_STATUS, `APB4_JPEG__PERF_CONTROL,
`APB4_JPEG__JOB_CONFIG, `APB4_JPEG__IMAGE_SIZE, `APB4_JPEG__INPUT_FORMAT,
`APB4_JPEG__OUTPUT_FORMAT, `APB4_JPEG__ENCODE_CONFIG,
`APB4_JPEG__RESTART_INTERVAL, `APB4_JPEG__BITSTREAM_ADDR,
`APB4_JPEG__BITSTREAM_SIZE, `APB4_JPEG__PLANE0_ADDR,
`APB4_JPEG__PLANE0_STRIDE, `APB4_JPEG__PLANE1_ADDR,
`APB4_JPEG__PLANE1_STRIDE, `APB4_JPEG__PLANE2_ADDR,
`APB4_JPEG__PLANE2_STRIDE, `APB4_JPEG__METADATA_ADDR,
`APB4_JPEG__METADATA_LENGTH, `APB4_JPEG__RING_BASE, `APB4_JPEG__RING_SIZE,
`APB4_JPEG__RING_TAIL, `APB4_JPEG__RING_CONTROL, `APB4_JPEG__IRQ_COALESCE,
`APB4_JPEG__DOORBELL, `APB4_JPEG__TABLE_CONTEXT, `APB4_JPEG__TABLE_KIND,
`APB4_JPEG__TABLE_INDEX, `APB4_JPEG__TABLE_DATA, `APB4_JPEG__TABLE_COMMAND: begin
s_access_err = s_access_err;
end
default: s_access_err = 1'b1;
endcase
if (busy_i && (((s_offset >= `APB4_JPEG__JOB_CONFIG) &&
(s_offset <= `APB4_JPEG__METADATA_LENGTH)) ||
(s_offset == `APB4_JPEG__RING_BASE) ||
(s_offset == `APB4_JPEG__RING_SIZE) ||
(s_offset == `APB4_JPEG__RING_CONTROL) ||
(s_offset == `APB4_JPEG__IRQ_COALESCE) ||
((s_offset >= `APB4_JPEG__TABLE_CONTEXT) &&
(s_offset <= `APB4_JPEG__TABLE_COMMAND)))) begin
s_access_err = 1'b1;
end
if ((s_offset == `APB4_JPEG__COMMAND) &&
((s_write_value[`APB4_JPEG__COMMAND_START] && (busy_i || quiesce_i)) ||
(s_write_value[`APB4_JPEG__COMMAND_ABORT] && !busy_i) ||
(s_write_value[`APB4_JPEG__COMMAND_SOFT_RESET] && busy_i))) begin
s_access_err = 1'b1;
end
if ((s_offset == `APB4_JPEG__TABLE_CONTEXT) && (s_write_value > 32'd3)) begin
s_access_err = 1'b1;
end
if ((s_offset == `APB4_JPEG__TABLE_KIND) && (s_write_value > 32'd11)) begin
s_access_err = 1'b1;
end
end else if (s_req) begin
unique case (s_offset)
`APB4_JPEG__IP_ID, `APB4_JPEG__IP_VERSION, `APB4_JPEG__CAPABILITY0,
`APB4_JPEG__CAPABILITY1, `APB4_JPEG__STATUS, `APB4_JPEG__IRQ_STATE,
`APB4_JPEG__IRQ_ENABLE, `APB4_JPEG__ERROR_STATUS, `APB4_JPEG__ERROR_ADDRESS,
`APB4_JPEG__ERROR_DETAIL, `APB4_JPEG__PERF_CONTROL, `APB4_JPEG__CYCLES_LO,
`APB4_JPEG__CYCLES_HI, `APB4_JPEG__PIXELS, `APB4_JPEG__INPUT_BYTES,
`APB4_JPEG__OUTPUT_BYTES, `APB4_JPEG__READ_STALL, `APB4_JPEG__WRITE_STALL,
`APB4_JPEG__JOB_CONFIG, `APB4_JPEG__IMAGE_SIZE, `APB4_JPEG__INPUT_FORMAT,
`APB4_JPEG__OUTPUT_FORMAT, `APB4_JPEG__ENCODE_CONFIG,
`APB4_JPEG__RESTART_INTERVAL, `APB4_JPEG__BITSTREAM_ADDR,
`APB4_JPEG__BITSTREAM_SIZE, `APB4_JPEG__PLANE0_ADDR,
`APB4_JPEG__PLANE0_STRIDE, `APB4_JPEG__PLANE1_ADDR,
`APB4_JPEG__PLANE1_STRIDE, `APB4_JPEG__PLANE2_ADDR,
`APB4_JPEG__PLANE2_STRIDE, `APB4_JPEG__METADATA_ADDR,
`APB4_JPEG__METADATA_LENGTH, `APB4_JPEG__RESULT_SIZE,
`APB4_JPEG__RESULT_IMAGE_SIZE, `APB4_JPEG__RESULT_FORMAT,
`APB4_JPEG__RESULT_MARKERS, `APB4_JPEG__RING_BASE, `APB4_JPEG__RING_SIZE,
`APB4_JPEG__RING_HEAD, `APB4_JPEG__RING_TAIL, `APB4_JPEG__RING_CONTROL,
`APB4_JPEG__RING_STATUS, `APB4_JPEG__IRQ_COALESCE,
`APB4_JPEG__TABLE_CONTEXT, `APB4_JPEG__TABLE_KIND,
`APB4_JPEG__TABLE_INDEX, `APB4_JPEG__TABLE_DATA, `APB4_JPEG__TABLE_STATUS: begin
s_access_err = s_access_err;
end
default: s_access_err = 1'b1;
endcase
end
end
always_comb begin
s_irq_clear = 5'd0;
s_irq_state_d = s_irq_state_q | s_irq_event;
s_irq_en_d = s_irq_en_q;
s_err_stat_d = s_err_stat_q;
s_err_addr_d = s_err_addr_q;
s_err_detail_d = s_err_detail_q;
s_perf_control_d = s_perf_control_q;
s_cfg_d = s_cfg_q;
s_ring_d = s_ring_q;
s_table_context_d = s_table_context_q;
s_table_kind_d = s_table_kind_q;
s_table_index_d = s_table_index_q;
start_o = 1'b0;
abort_o = 1'b0;
soft_reset_o = 1'b0;
ring_kick_o = 1'b0;
table_write_data_o = s_write_value;
table_write_o = 1'b0;
table_commit_o = 1'b0;
table_default_o = 1'b0;
table_clear_o = 1'b0;
perf_clear_o = 1'b0;
if (s_write && !s_access_err) begin
unique case (s_offset)
`APB4_JPEG__COMMAND: begin
start_o = s_write_value[`APB4_JPEG__COMMAND_START];
abort_o = s_write_value[`APB4_JPEG__COMMAND_ABORT];
soft_reset_o = s_write_value[`APB4_JPEG__COMMAND_SOFT_RESET];
ring_kick_o = s_write_value[`APB4_JPEG__COMMAND_RING_KICK];
end
`APB4_JPEG__IRQ_STATE: s_irq_clear = s_write_value[4:0];
`APB4_JPEG__IRQ_ENABLE:
s_irq_en_d = 5'(apply_wstrb({27'd0, s_irq_en_q}, s_write_value, apb4.pstrb));
`APB4_JPEG__IRQ_TEST: s_irq_state_d |= s_write_value[4:0];
`APB4_JPEG__ERROR_STATUS: begin
if (s_write_value[0]) begin
s_err_stat_d = 32'd0;
s_err_addr_d = 32'd0;
s_err_detail_d = 32'd0;
end
end
`APB4_JPEG__PERF_CONTROL: begin
s_perf_control_d = 2'(apply_wstrb({30'd0, s_perf_control_q}, s_write_value, apb4.pstrb));
perf_clear_o = s_write_value[1];
end
`APB4_JPEG__JOB_CONFIG: s_cfg_d[0] = apply_wstrb(s_cfg_q[0], s_write_value, apb4.pstrb);
`APB4_JPEG__IMAGE_SIZE: s_cfg_d[1] = apply_wstrb(s_cfg_q[1], s_write_value, apb4.pstrb);
`APB4_JPEG__INPUT_FORMAT: s_cfg_d[2] = apply_wstrb(s_cfg_q[2], s_write_value, apb4.pstrb);
`APB4_JPEG__OUTPUT_FORMAT: s_cfg_d[3] = apply_wstrb(s_cfg_q[3], s_write_value, apb4.pstrb);
`APB4_JPEG__ENCODE_CONFIG: s_cfg_d[4] = apply_wstrb(s_cfg_q[4], s_write_value, apb4.pstrb);
`APB4_JPEG__RESTART_INTERVAL:
s_cfg_d[5] = apply_wstrb(s_cfg_q[5], s_write_value, apb4.pstrb);
`APB4_JPEG__BITSTREAM_ADDR: s_cfg_d[6] = apply_wstrb(s_cfg_q[6], s_write_value, apb4.pstrb);
`APB4_JPEG__BITSTREAM_SIZE: s_cfg_d[7] = apply_wstrb(s_cfg_q[7], s_write_value, apb4.pstrb);
`APB4_JPEG__PLANE0_ADDR: s_cfg_d[8] = apply_wstrb(s_cfg_q[8], s_write_value, apb4.pstrb);
`APB4_JPEG__PLANE0_STRIDE: s_cfg_d[9] = apply_wstrb(s_cfg_q[9], s_write_value, apb4.pstrb);
`APB4_JPEG__PLANE1_ADDR: s_cfg_d[10] = apply_wstrb(s_cfg_q[10], s_write_value, apb4.pstrb);
`APB4_JPEG__PLANE1_STRIDE:
s_cfg_d[11] = apply_wstrb(s_cfg_q[11], s_write_value, apb4.pstrb);
`APB4_JPEG__PLANE2_ADDR: s_cfg_d[12] = apply_wstrb(s_cfg_q[12], s_write_value, apb4.pstrb);
`APB4_JPEG__PLANE2_STRIDE:
s_cfg_d[13] = apply_wstrb(s_cfg_q[13], s_write_value, apb4.pstrb);
`APB4_JPEG__METADATA_ADDR:
s_cfg_d[14] = apply_wstrb(s_cfg_q[14], s_write_value, apb4.pstrb);
`APB4_JPEG__METADATA_LENGTH:
s_cfg_d[15] = apply_wstrb(s_cfg_q[15], s_write_value, apb4.pstrb);
`APB4_JPEG__RING_BASE: s_ring_d[0] = apply_wstrb(s_ring_q[0], s_write_value, apb4.pstrb);
`APB4_JPEG__RING_SIZE: s_ring_d[1] = apply_wstrb(s_ring_q[1], s_write_value, apb4.pstrb);
`APB4_JPEG__RING_TAIL: s_ring_d[2] = apply_wstrb(s_ring_q[2], s_write_value, apb4.pstrb);
`APB4_JPEG__RING_CONTROL: s_ring_d[3] = apply_wstrb(s_ring_q[3], s_write_value, apb4.pstrb);
`APB4_JPEG__IRQ_COALESCE: s_ring_d[4] = apply_wstrb(s_ring_q[4], s_write_value, apb4.pstrb);
`APB4_JPEG__DOORBELL: ring_kick_o = s_write_value[0];
`APB4_JPEG__TABLE_CONTEXT: s_table_context_d = s_write_value[1:0];
`APB4_JPEG__TABLE_KIND: s_table_kind_d = s_write_value[3:0];
`APB4_JPEG__TABLE_INDEX: s_table_index_d = s_write_value[7:0];
`APB4_JPEG__TABLE_DATA: begin
table_write_o = 1'b1;
s_table_index_d = s_table_index_q + 1'b1;
end
`APB4_JPEG__TABLE_COMMAND: begin
table_commit_o = s_write_value[`APB4_JPEG__TABLE_COMMAND_COMMIT];
table_default_o = s_write_value[`APB4_JPEG__TABLE_COMMAND_DEFAULT];
table_clear_o = s_write_value[`APB4_JPEG__TABLE_COMMAND_CLEAR];
end
default: begin
end
endcase
end
s_irq_state_d = (s_irq_state_d & ~s_irq_clear) | s_irq_event;
if (error_event_i && !s_err_stat_q[0]) begin
s_err_stat_d = {20'd0, error_axi_resp_i, error_stage_i, error_code_i, 1'b1};
s_err_addr_d = error_addr_i;
s_err_detail_d = error_detail_i;
end
if (soft_reset_o) begin
s_irq_state_d = 5'd0;
s_err_stat_d = 32'd0;
s_err_addr_d = 32'd0;
s_err_detail_d = 32'd0;
end
end
dffr #(
.DATA_WIDTH(5)
) u_irq_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_irq_state_d),
.dat_o (s_irq_state_q)
);
dffr #(
.DATA_WIDTH(5)
) u_irq_en_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_irq_en_d),
.dat_o (s_irq_en_q)
);
dffr #(
.DATA_WIDTH(32)
) u_err_stat_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_stat_d),
.dat_o (s_err_stat_q)
);
dffr #(
.DATA_WIDTH(32)
) u_err_addr_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_addr_d),
.dat_o (s_err_addr_q)
);
dffr #(
.DATA_WIDTH(32)
) u_err_detail_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_detail_d),
.dat_o (s_err_detail_q)
);
dffr #(
.DATA_WIDTH(2)
) u_perf_control_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_perf_control_d),
.dat_o (s_perf_control_q)
);
dffr #(
.DATA_WIDTH(16 * 32)
) u_cfg_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_cfg_d),
.dat_o (s_cfg_q)
);
dffr #(
.DATA_WIDTH(5 * 32)
) u_ring_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_ring_d),
.dat_o (s_ring_q)
);
dffr #(
.DATA_WIDTH(2)
) u_table_context_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_table_context_d),
.dat_o (s_table_context_q)
);
dffr #(
.DATA_WIDTH(4)
) u_table_kind_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_table_kind_d),
.dat_o (s_table_kind_q)
);
dffr #(
.DATA_WIDTH(8)
) u_table_index_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_table_index_d),
.dat_o (s_table_index_q)
);
endmodule
+322
View File
@@ -0,0 +1,322 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_table_cache #(
parameter int unsigned ReciprocalWidth = 25
) (
// verilog_format: off -- preserve command, table lookup, and cached table columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic [ 1:0] context_i,
input logic [ 1:0] quant_id_i,
input logic [ 1:0] dc_id_i,
input logic [ 1:0] ac_id_i,
output logic start_ready_o,
output logic lookup_o,
output logic [ 1:0] lookup_context_o,
output logic [ 3:0] lookup_kind_o,
output logic [ 7:0] lookup_index_o,
input logic [31:0] lookup_data_i,
input logic lookup_valid_i,
input logic lookup_err_i,
output logic [64*8-1:0] quant_o,
output logic [64*ReciprocalWidth-1:0] reciprocal_o,
output logic [12*16-1:0] dc_code_o,
output logic [12*5-1:0] dc_length_o,
output logic [256*16-1:0] ac_code_o,
output logic [256*5-1:0] ac_length_o,
output logic result_valid_o,
input logic result_ready_i,
output logic error_o
// verilog_format: on
);
typedef enum logic [2:0] {
Idle,
QuantIssue,
QuantWait,
DcIssue,
DcWait,
AcIssue,
AcWait,
Result
} state_e;
state_e s_state_d;
state_e s_state_q;
logic [ 2:0] s_state_bits_q;
logic [ 1:0] s_context_d;
logic [ 1:0] s_context_q;
logic [ 1:0] s_quant_id_d;
logic [ 1:0] s_quant_id_q;
logic [ 1:0] s_dc_id_d;
logic [ 1:0] s_dc_id_q;
logic [ 1:0] s_ac_id_d;
logic [ 1:0] s_ac_id_q;
logic [ 7:0] s_index_d;
logic [ 7:0] s_index_q;
logic [ 64*8-1:0] s_quant_d;
logic [ 64*8-1:0] s_quant_q;
logic [64*ReciprocalWidth-1:0] s_reciprocal_d;
logic [64*ReciprocalWidth-1:0] s_reciprocal_q;
logic [ 12*16-1:0] s_dc_code_d;
logic [ 12*16-1:0] s_dc_code_q;
logic [ 12*5-1:0] s_dc_len_d;
logic [ 12*5-1:0] s_dc_len_q;
logic [ 256*16-1:0] s_ac_code_d;
logic [ 256*16-1:0] s_ac_code_q;
logic [ 256*5-1:0] s_ac_len_d;
logic [ 256*5-1:0] s_ac_len_q;
logic s_err_d;
logic s_err_q;
logic [ 7:0] s_quant_value;
logic [ ReciprocalWidth-1:0] s_reciprocal_value;
function automatic logic [ReciprocalWidth-1:0] reciprocal_value(input logic [7:0] quant_i);
logic [ReciprocalWidth:0] s_numerator;
begin
if (quant_i == 8'd0) begin
return '0;
end
s_numerator = (ReciprocalWidth + 1)'(1 << 24) + (ReciprocalWidth + 1)'(quant_i) - 1'b1;
return ReciprocalWidth'(s_numerator / (ReciprocalWidth + 1)'(quant_i));
end
endfunction
assign s_state_q = state_e'(s_state_bits_q);
assign start_ready_o = s_state_q == Idle;
assign lookup_o = (s_state_q == QuantIssue) || (s_state_q == DcIssue) || (s_state_q == AcIssue);
assign lookup_context_o = s_context_q;
assign lookup_kind_o = (s_state_q == QuantIssue) ? {2'd0, s_quant_id_q} :
(s_state_q == DcIssue) ? (4'd4 + {2'd0, s_dc_id_q}) :
(4'd8 + {2'd0, s_ac_id_q});
assign lookup_index_o = s_index_q;
assign quant_o = s_quant_q;
assign reciprocal_o = s_reciprocal_q;
assign dc_code_o = s_dc_code_q;
assign dc_length_o = s_dc_len_q;
assign ac_code_o = s_ac_code_q;
assign ac_length_o = s_ac_len_q;
assign result_valid_o = s_state_q == Result;
assign error_o = s_err_q;
assign s_quant_value = lookup_data_i[7:0];
assign s_reciprocal_value = (lookup_data_i[31:8] != 24'd0) ?
{1'b0, lookup_data_i[31:8]} :
reciprocal_value(
lookup_data_i[7:0]
);
always_comb begin
s_state_d = s_state_q;
s_context_d = s_context_q;
s_quant_id_d = s_quant_id_q;
s_dc_id_d = s_dc_id_q;
s_ac_id_d = s_ac_id_q;
s_index_d = s_index_q;
s_quant_d = s_quant_q;
s_reciprocal_d = s_reciprocal_q;
s_dc_code_d = s_dc_code_q;
s_dc_len_d = s_dc_len_q;
s_ac_code_d = s_ac_code_q;
s_ac_len_d = s_ac_len_q;
s_err_d = s_err_q;
unique case (s_state_q)
Idle: begin
if (start_i) begin
s_context_d = context_i;
s_quant_id_d = quant_id_i;
s_dc_id_d = dc_id_i;
s_ac_id_d = ac_id_i;
s_index_d = 8'd0;
s_quant_d = '0;
s_reciprocal_d = '0;
s_dc_code_d = '0;
s_dc_len_d = '0;
s_ac_code_d = '0;
s_ac_len_d = '0;
s_err_d = 1'b0;
s_state_d = QuantIssue;
end
end
QuantIssue: begin
s_state_d = QuantWait;
end
QuantWait: begin
if (lookup_valid_i || lookup_err_i) begin
if (lookup_err_i || (s_quant_value == 8'd0)) begin
s_err_d = 1'b1;
end else begin
s_quant_d[s_index_q*8+:8] = s_quant_value;
s_reciprocal_d[s_index_q*ReciprocalWidth+:ReciprocalWidth] = s_reciprocal_value;
end
if (s_index_q == 8'd63) begin
s_index_d = 8'd0;
s_state_d = DcIssue;
end else begin
s_index_d = s_index_q + 1'b1;
s_state_d = QuantIssue;
end
end
end
DcIssue: begin
s_state_d = DcWait;
end
DcWait: begin
if (lookup_valid_i || lookup_err_i) begin
if (lookup_err_i || (lookup_data_i[20:16] == 5'd0)) begin
s_err_d = 1'b1;
end else begin
s_dc_code_d[s_index_q*16+:16] = lookup_data_i[15:0];
s_dc_len_d[s_index_q*5+:5] = lookup_data_i[20:16];
end
if (s_index_q == 8'd11) begin
s_index_d = 8'd0;
s_state_d = AcIssue;
end else begin
s_index_d = s_index_q + 1'b1;
s_state_d = DcIssue;
end
end
end
AcIssue: begin
s_state_d = AcWait;
end
AcWait: begin
if (lookup_valid_i || lookup_err_i) begin
if (lookup_err_i) begin
s_err_d = 1'b1;
end else begin
s_ac_code_d[s_index_q*16+:16] = lookup_data_i[15:0];
s_ac_len_d[s_index_q*5+:5] = lookup_data_i[20:16];
end
if (s_index_q == 8'hff) begin
s_index_d = 8'd0;
s_state_d = Result;
end else begin
s_index_d = s_index_q + 1'b1;
s_state_d = AcIssue;
end
end
end
Result: begin
if (result_ready_i) begin
s_state_d = Idle;
end
end
default: s_state_d = Idle;
endcase
end
dffr #(
.DATA_WIDTH(3)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(2)
) u_context_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_context_d),
.dat_o (s_context_q)
);
dffr #(
.DATA_WIDTH(2)
) u_quant_id_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_quant_id_d),
.dat_o (s_quant_id_q)
);
dffr #(
.DATA_WIDTH(2)
) u_dc_id_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dc_id_d),
.dat_o (s_dc_id_q)
);
dffr #(
.DATA_WIDTH(2)
) u_ac_id_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_ac_id_d),
.dat_o (s_ac_id_q)
);
dffr #(
.DATA_WIDTH(8)
) u_index_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_index_d),
.dat_o (s_index_q)
);
dffr #(
.DATA_WIDTH(64 * 8)
) u_quant_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_quant_d),
.dat_o (s_quant_q)
);
dffr #(
.DATA_WIDTH(64 * ReciprocalWidth)
) u_reciprocal_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_reciprocal_d),
.dat_o (s_reciprocal_q)
);
dffr #(
.DATA_WIDTH(12 * 16)
) u_dc_code_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dc_code_d),
.dat_o (s_dc_code_q)
);
dffr #(
.DATA_WIDTH(12 * 5)
) u_dc_len_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_dc_len_d),
.dat_o (s_dc_len_q)
);
dffr #(
.DATA_WIDTH(256 * 16)
) u_ac_code_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_ac_code_d),
.dat_o (s_ac_code_q)
);
dffr #(
.DATA_WIDTH(256 * 5)
) u_ac_len_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_ac_len_d),
.dat_o (s_ac_len_q)
);
dffr u_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_d),
.dat_o (s_err_q)
);
`ifndef SYNTHESIS
initial begin
if (ReciprocalWidth != 25) begin
$fatal(1, "jpeg_table_cache: reciprocal width must be 25 bits");
end
end
`endif
endmodule
+206
View File
@@ -0,0 +1,206 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_table_store (
// verilog_format: off -- preserve portal, engine lookup, and status interface columns
input logic clk_i,
input logic rst_n_i,
input logic [ 1:0] portal_context_i,
input logic [ 3:0] portal_kind_i,
input logic [ 7:0] portal_index_i,
input logic [31:0] portal_write_data_i,
input logic portal_write_i,
input logic portal_commit_i,
input logic portal_default_i,
input logic portal_clear_i,
output logic [31:0] portal_read_data_o,
output logic [31:0] portal_status_o,
input logic lookup_i,
input logic [ 1:0] lookup_context_i,
input logic [ 3:0] lookup_kind_i,
input logic [ 7:0] lookup_index_i,
output logic [31:0] lookup_data_o,
output logic lookup_valid_o,
output logic lookup_err_o
// verilog_format: on
);
localparam int unsigned EntriesPerContext = 1328;
localparam int unsigned NumBanks = 6;
logic [ 13:0] s_portal_addr;
logic s_portal_addr_valid;
logic [ 13:0] s_lookup_addr;
logic s_lookup_addr_valid;
logic [ 13:0] s_selected_addr;
logic s_selected_valid;
logic [ 2:0] s_selected_bank;
logic [ 9:0] s_selected_word;
logic [ 2:0] s_read_bank_d;
logic [ 2:0] s_read_bank_q;
logic s_lookup_pending_d;
logic s_lookup_pending_q;
logic s_lookup_err_d;
logic s_lookup_err_q;
logic [ 3:0] s_context_valid_d;
logic [ 3:0] s_context_valid_q;
logic [ 3:0] s_context_dirty_d;
logic [ 3:0] s_context_dirty_q;
logic [ 3:0] s_context_err_d;
logic [ 3:0] s_context_err_q;
logic [NumBanks-1:0] s_bank_cs;
logic [NumBanks-1:0] s_bank_write;
logic [NumBanks-1:0][31:0] s_bank_read_data;
function automatic logic [14:0] table_address(
input logic [1:0] context_i, input logic [3:0] kind_i, input logic [7:0] index_i);
logic [14:0] s_addr;
logic s_valid;
begin
s_addr = 15'(context_i * EntriesPerContext);
s_valid = 1'b1;
if (kind_i < 4) begin
s_addr += 15'(int'(kind_i) * 64) + 15'(index_i);
s_valid = index_i < 8'd64;
end else if (kind_i < 8) begin
s_addr += 15'd256 + 15'((int'(kind_i) - 4) * 12) + 15'(index_i);
s_valid = index_i < 8'd12;
end else if (kind_i < 12) begin
s_addr += 15'd304 + 15'((int'(kind_i) - 8) * 256) + 15'(index_i);
end else begin
s_valid = 1'b0;
end
return {s_valid, s_addr[13:0]};
end
endfunction
always_comb begin
{s_portal_addr_valid, s_portal_addr} =
table_address(portal_context_i, portal_kind_i, portal_index_i);
{s_lookup_addr_valid, s_lookup_addr} =
table_address(lookup_context_i, lookup_kind_i, lookup_index_i);
if (lookup_i) begin
s_selected_addr = s_lookup_addr;
s_selected_valid = s_lookup_addr_valid;
end else begin
s_selected_addr = s_portal_addr;
s_selected_valid = s_portal_addr_valid;
end
s_selected_bank = s_selected_addr[12:10];
s_selected_word = s_selected_addr[9:0];
end
always_comb begin
s_bank_cs = '0;
s_bank_write = '0;
if (s_selected_valid && (s_selected_bank < 3'(NumBanks))) begin
s_bank_cs[s_selected_bank] = 1'b1;
s_bank_write[s_selected_bank] = portal_write_i && !lookup_i;
end
end
assign portal_read_data_o = (s_read_bank_q < 3'(NumBanks)) ?
s_bank_read_data[s_read_bank_q] : 32'd0;
assign lookup_data_o = portal_read_data_o;
assign lookup_valid_o = s_lookup_pending_q;
assign lookup_err_o = s_lookup_err_q;
assign portal_status_o = {
20'd0,
s_context_err_q[portal_context_i],
s_context_dirty_q[portal_context_i],
6'd0,
s_context_valid_q[portal_context_i],
3'd0
};
always_comb begin
s_read_bank_d = s_read_bank_q;
s_lookup_pending_d = lookup_i && s_lookup_addr_valid && s_context_valid_q[lookup_context_i];
s_lookup_err_d = lookup_i && (!s_lookup_addr_valid || !s_context_valid_q[lookup_context_i]);
if (s_selected_valid && (s_selected_bank < 3'(NumBanks))) begin
s_read_bank_d = s_selected_bank;
end
s_context_valid_d = s_context_valid_q;
s_context_dirty_d = s_context_dirty_q;
s_context_err_d = s_context_err_q;
if (portal_write_i) begin
s_context_valid_d[portal_context_i] = 1'b0;
s_context_dirty_d[portal_context_i] = 1'b1;
if (!s_portal_addr_valid) begin
s_context_err_d[portal_context_i] = 1'b1;
end
end
if (portal_commit_i) begin
s_context_valid_d[portal_context_i] = s_context_dirty_q[portal_context_i] &&
!s_context_err_q[portal_context_i];
s_context_dirty_d[portal_context_i] = 1'b0;
end
if (portal_default_i) begin
s_context_valid_d[portal_context_i] = 1'b0;
s_context_dirty_d[portal_context_i] = 1'b0;
s_context_err_d[portal_context_i] = 1'b1;
end
if (portal_clear_i) begin
s_context_valid_d[portal_context_i] = 1'b0;
s_context_dirty_d[portal_context_i] = 1'b0;
s_context_err_d[portal_context_i] = 1'b0;
end
end
for (genvar bank = 0; bank < NumBanks; bank++) begin : gen_table_bank
tc_sram_1024x32 u_table_sram (
.clk_i (clk_i),
.cs_i (s_bank_cs[bank]),
.addr_i(s_selected_word),
.data_i(portal_write_data_i),
.mask_i(4'hf),
.wren_i(s_bank_write[bank]),
.data_o(s_bank_read_data[bank])
);
end
dffr #(
.DATA_WIDTH(3)
) u_read_bank_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_read_bank_d),
.dat_o (s_read_bank_q)
);
dffr u_lookup_pending_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_lookup_pending_d),
.dat_o (s_lookup_pending_q)
);
dffr u_lookup_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_lookup_err_d),
.dat_o (s_lookup_err_q)
);
dffr #(
.DATA_WIDTH(4)
) u_context_valid_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_context_valid_d),
.dat_o (s_context_valid_q)
);
dffr #(
.DATA_WIDTH(4)
) u_context_dirty_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_context_dirty_d),
.dat_o (s_context_dirty_q)
);
dffr #(
.DATA_WIDTH(4)
) u_context_err_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_context_err_d),
.dat_o (s_context_err_q)
);
endmodule
+263
View File
@@ -0,0 +1,263 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
module jpeg_transform #(
parameter int unsigned ElementWidth = 24
) (
// verilog_format: off -- preserve the block-transform interface columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic inverse_i,
input logic [64*ElementWidth-1:0] block_i,
output logic start_ready_o,
output logic [64*ElementWidth-1:0] block_o,
output logic result_valid_o,
input logic result_ready_i
// verilog_format: on
);
typedef enum logic [1:0] {
Idle,
FirstPass,
SecondPass,
Result
} state_e;
state_e s_state_d;
state_e s_state_q;
logic [ 1:0] s_state_bits_q;
logic [ 2:0] s_index_d;
logic [ 2:0] s_index_q;
logic s_inverse_d;
logic s_inverse_q;
logic [64*ElementWidth-1:0] s_input_d;
logic [64*ElementWidth-1:0] s_input_q;
logic [64*ElementWidth-1:0] s_temp_d;
logic [64*ElementWidth-1:0] s_temp_q;
logic [64*ElementWidth-1:0] s_result_d;
logic [64*ElementWidth-1:0] s_result_q;
logic [ 8*ElementWidth-1:0] s_vector;
function automatic logic signed [15:0] cosine_value(input logic [2:0] frequency_i,
input logic [2:0] sample_i);
begin
unique case ({
frequency_i, sample_i
})
6'o00, 6'o01, 6'o02, 6'o03, 6'o04, 6'o05, 6'o06, 6'o07: cosine_value = 16'sd11585;
6'o10: cosine_value = 16'sd16069;
6'o11: cosine_value = 16'sd13623;
6'o12: cosine_value = 16'sd9102;
6'o13: cosine_value = 16'sd3196;
6'o14: cosine_value = -16'sd3196;
6'o15: cosine_value = -16'sd9102;
6'o16: cosine_value = -16'sd13623;
6'o17: cosine_value = -16'sd16069;
6'o20: cosine_value = 16'sd15137;
6'o21: cosine_value = 16'sd6270;
6'o22: cosine_value = -16'sd6270;
6'o23: cosine_value = -16'sd15137;
6'o24: cosine_value = -16'sd15137;
6'o25: cosine_value = -16'sd6270;
6'o26: cosine_value = 16'sd6270;
6'o27: cosine_value = 16'sd15137;
6'o30: cosine_value = 16'sd13623;
6'o31: cosine_value = -16'sd3196;
6'o32: cosine_value = -16'sd16069;
6'o33: cosine_value = -16'sd9102;
6'o34: cosine_value = 16'sd9102;
6'o35: cosine_value = 16'sd16069;
6'o36: cosine_value = 16'sd3196;
6'o37: cosine_value = -16'sd13623;
6'o40, 6'o43, 6'o44, 6'o47: cosine_value = 16'sd11585;
6'o41, 6'o42, 6'o45, 6'o46: cosine_value = -16'sd11585;
6'o50: cosine_value = 16'sd9102;
6'o51: cosine_value = -16'sd16069;
6'o52: cosine_value = 16'sd3196;
6'o53: cosine_value = 16'sd13623;
6'o54: cosine_value = -16'sd13623;
6'o55: cosine_value = -16'sd3196;
6'o56: cosine_value = 16'sd16069;
6'o57: cosine_value = -16'sd9102;
6'o60: cosine_value = 16'sd6270;
6'o61: cosine_value = -16'sd15137;
6'o62: cosine_value = 16'sd15137;
6'o63: cosine_value = -16'sd6270;
6'o64: cosine_value = -16'sd6270;
6'o65: cosine_value = 16'sd15137;
6'o66: cosine_value = -16'sd15137;
6'o67: cosine_value = 16'sd6270;
6'o70: cosine_value = 16'sd3196;
6'o71: cosine_value = -16'sd9102;
6'o72: cosine_value = 16'sd13623;
6'o73: cosine_value = -16'sd16069;
6'o74: cosine_value = 16'sd16069;
6'o75: cosine_value = -16'sd13623;
6'o76: cosine_value = 16'sd9102;
6'o77: cosine_value = -16'sd3196;
default: cosine_value = 16'sd0;
endcase
end
endfunction
function automatic logic signed [ElementWidth-1:0] rounded_scale(
input logic signed [47:0] value_i);
logic signed [47:0] s_magnitude;
logic signed [47:0] s_scaled;
begin
if (value_i < 48'sd0) begin
s_magnitude = -value_i;
s_scaled = (s_magnitude + 48'sd16384) >>> 15;
rounded_scale = -ElementWidth'(s_scaled);
end else begin
s_scaled = (value_i + 48'sd16384) >>> 15;
rounded_scale = ElementWidth'(s_scaled);
end
end
endfunction
function automatic logic signed [ElementWidth-1:0] transform_1d(
input logic [8*ElementWidth-1:0] vector_i, input logic [2:0] output_i, input logic inverse_i);
logic signed [ 47:0] s_accumulator;
logic signed [ElementWidth-1:0] s_sample;
logic signed [ 15:0] s_cosine;
begin
s_accumulator = 48'sd0;
for (int unsigned index = 0; index < 8; index++) begin
s_sample = vector_i[index*ElementWidth+:ElementWidth];
s_cosine = inverse_i ? cosine_value(3'(index), output_i) :
cosine_value(output_i, 3'(index));
s_accumulator += s_sample * s_cosine;
end
return rounded_scale(s_accumulator);
end
endfunction
assign s_state_q = state_e'(s_state_bits_q);
assign start_ready_o = s_state_q == Idle;
assign block_o = s_result_q;
assign result_valid_o = s_state_q == Result;
always_comb begin
s_state_d = s_state_q;
s_index_d = s_index_q;
s_inverse_d = s_inverse_q;
s_input_d = s_input_q;
s_temp_d = s_temp_q;
s_result_d = s_result_q;
s_vector = '0;
unique case (s_state_q)
Idle: begin
s_index_d = 3'd0;
if (start_i) begin
s_input_d = block_i;
s_inverse_d = inverse_i;
s_temp_d = '0;
s_result_d = '0;
s_state_d = FirstPass;
end
end
FirstPass: begin
for (int unsigned index = 0; index < 8; index++) begin
s_vector[index*ElementWidth+:ElementWidth] =
s_input_q[((s_index_q*8)+index)*ElementWidth+:ElementWidth];
end
for (int unsigned output_index = 0; output_index < 8; output_index++) begin
s_temp_d[((s_index_q*8)+output_index)*ElementWidth+:ElementWidth] =
transform_1d(s_vector, 3'(output_index), s_inverse_q);
end
if (s_index_q == 3'd7) begin
s_index_d = 3'd0;
s_state_d = SecondPass;
end else begin
s_index_d = s_index_q + 1'b1;
end
end
SecondPass: begin
for (int unsigned index = 0; index < 8; index++) begin
s_vector[index*ElementWidth+:ElementWidth] =
s_temp_q[((index*8)+int'(s_index_q))*ElementWidth+:ElementWidth];
end
for (int unsigned output_index = 0; output_index < 8; output_index++) begin
s_result_d[((output_index*8)+int'(s_index_q))*ElementWidth+:ElementWidth] =
transform_1d(s_vector, 3'(output_index), s_inverse_q);
end
if (s_index_q == 3'd7) begin
s_index_d = 3'd0;
s_state_d = Result;
end else begin
s_index_d = s_index_q + 1'b1;
end
end
Result: begin
if (result_ready_i) begin
s_state_d = Idle;
end
end
default: begin
s_state_d = Idle;
end
endcase
end
dffr #(
.DATA_WIDTH(2)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(3)
) u_index_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_index_d),
.dat_o (s_index_q)
);
dffr u_inverse_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_inverse_d),
.dat_o (s_inverse_q)
);
dffr #(
.DATA_WIDTH(64 * ElementWidth)
) u_input_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_input_d),
.dat_o (s_input_q)
);
dffr #(
.DATA_WIDTH(64 * ElementWidth)
) u_temp_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_temp_d),
.dat_o (s_temp_q)
);
dffr #(
.DATA_WIDTH(64 * ElementWidth)
) u_result_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_result_d),
.dat_o (s_result_q)
);
`ifndef SV_ASSRT_DISABLE
always_ff @(posedge clk_i) begin
if (rst_n_i) begin
assert (!(start_i && !start_ready_o));
if (result_valid_o && !result_ready_i) begin
assert ($stable(block_o));
end
end
end
`endif
endmodule
+9
View File
@@ -498,6 +498,15 @@
"public": true,
"user_access": "none"
},
{
"symbol": "APB4_JPEG",
"base": "0x1001A000",
"size": "0x00001000",
"route": "apb4_periph",
"kind": "active",
"public": true,
"user_access": "none"
},
{
"symbol": "APB4_PWM",
"base": "0x20002000",
+22
View File
@@ -125,6 +125,28 @@
/ip/multimedia/dvp_core.sv
/ip/multimedia/dvp_reg.sv
/ip/multimedia/axi4s_dvp.sv
/ip/multimedia/jpeg_pkg.sv
/ip/multimedia/jpeg_transform.sv
/ip/multimedia/jpeg_quantizer.sv
/ip/multimedia/jpeg_entropy_encoder.sv
/ip/multimedia/jpeg_entropy_decoder.sv
/ip/multimedia/jpeg_bit_packer.sv
/ip/multimedia/jpeg_bit_reader.sv
/ip/multimedia/jpeg_byte_unpacker.sv
/ip/multimedia/jpeg_byte_joiner.sv
/ip/multimedia/jpeg_header_writer.sv
/ip/multimedia/jpeg_marker_parser.sv
/ip/multimedia/jpeg_table_store.sv
/ip/multimedia/jpeg_table_cache.sv
/ip/multimedia/jpeg_block_encoder.sv
/ip/multimedia/jpeg_block_decoder.sv
/ip/multimedia/jpeg_mcu_builder.sv
/ip/multimedia/jpeg_mcu_reconstructor.sv
/ip/multimedia/jpeg_encode_core.sv
/ip/multimedia/jpeg_decode_core.sv
/ip/multimedia/jpeg_dma.sv
/ip/multimedia/jpeg_reg.sv
/ip/multimedia/apb4_jpeg.sv
/ip/peripheral/timer_core.sv
/ip/peripheral/timer_reg.sv
/ip/peripheral/apb4_timer.sv
@@ -109,6 +109,7 @@
{"name": "sram_config", "source": "pclk", "destination": "hp", "primitive": "apb4_async_bridge", "path": "rtl/mini/top/retrosoc.sv", "instance": "u_sram_cfg_cdc"},
{"name": "fabric_monitor", "source": "pclk", "destination": "hp", "primitive": "apb4_async_bridge", "path": "rtl/mini/top/retrosoc.sv", "instance": "u_fabric_monitor_cdc"},
{"name": "io_dma_data", "source": "pclk", "destination": "hp", "primitive": "axi4_async_bridge", "path": "rtl/mini/top/soc_data_plane.sv", "instance": "u_io_cdc"},
{"name": "jpeg_data", "source": "pclk", "destination": "hp", "primitive": "axi4_async_bridge", "path": "rtl/mini/top/soc_data_plane.sv", "instance": "u_jpeg_cdc"},
{"name": "extension_h_data", "source": "pclk", "destination": "hp", "primitive": "axi4_async_bridge", "path": "rtl/mini/top/soc_data_plane.sv", "instance": "u_ext_h_cdc"},
{"name": "sdram_data", "source": "hp", "destination": "memory", "primitive": "axi4_async_bridge", "path": "rtl/mini/top/soc_data_plane.sv", "instance": "u_target_cdc"},
{"name": "serial_memory_data", "source": "hp", "destination": "memory", "primitive": "axi4_async_bridge", "path": "rtl/mini/top/soc_data_plane.sv", "instance": "u_target_cdc"},
@@ -27,7 +27,7 @@ DATA_MASTER_NAMES = (
"io_gateway_a",
"io_gateway_b",
"lp_gateway",
"reserved",
"jpeg",
"ext_h",
)
DATA_TARGET_NAMES = ("sram", "sdram", "qpi", "opi", "xpi")
+20 -4
View File
@@ -5,7 +5,7 @@
"irq_groups": [
{
"name": "apb4_periph",
"width": 22,
"width": 23,
"top_signal": "s_apb4_periph_irq"
},
{
@@ -259,6 +259,15 @@
"signal": "s_ext_h_irq",
"user_visible": false
},
{
"name": "jpeg",
"description": "APB4 JPEG codec completion and error interrupt",
"group": "apb4_periph",
"group_bit": 22,
"core_bit": 30,
"signal": "resource_irq_lp_i[5]",
"user_visible": false
},
{
"name": "resource_fault",
"description": "Resource ownership and handoff fault interrupt",
@@ -378,9 +387,9 @@
},
{
"index": 6,
"name": "reserved",
"read_targets": [],
"write_targets": [],
"name": "jpeg",
"read_targets": ["sram", "sdram", "qpi", "opi", "xpi"],
"write_targets": ["sram", "sdram", "qpi", "opi"],
"allow_instruction": false,
"require_noncacheable": true
},
@@ -584,6 +593,13 @@
"timed_interface": "u_hp_plic_apb4_if",
"pure_interface": "u_hp_plic_apb4_pure_if",
"region": "HP_PLIC"
},
{
"slot": 26,
"name": "jpeg",
"timed_interface": "u_jpeg_apb4_if",
"pure_interface": "u_jpeg_apb4_pure_if",
"region": "APB4_JPEG"
}
],
"apb4_system_targets": [
+3 -1
View File
@@ -101,6 +101,8 @@ CRT_SRCS := $(ROOT_PATH)/crt/arch/riscv/startup.S \
$(ROOT_PATH)/crt/src/hal/dma_math.c \
$(ROOT_PATH)/crt/src/hal/dma.c \
$(ROOT_PATH)/crt/src/hal/crypto.c \
$(ROOT_PATH)/crt/src/hal/jpeg_math.c \
$(ROOT_PATH)/crt/src/hal/jpeg.c \
$(ROOT_PATH)/crt/src/hal/dvp.c \
$(ROOT_PATH)/crt/src/hal/perf.c \
$(ROOT_PATH)/crt/src/hal/lcd.c \
@@ -222,4 +224,4 @@ firmware: $(FIRMWARE_ELF)
firmware asm: | manifest
.PHONY: FORCE_VERSION
.PHONY: FORCE_VERSION
+23 -4
View File
@@ -23,8 +23,10 @@ module apb4_periph (
input logic debug_halted_i,
input logic timebase_tick_i,
input logic ext_h_hp_irq_i,
input logic [4:0] resource_irq_lp_i,
input logic [4:0] resource_irq_hp_i,
input logic [5:0] resource_irq_lp_i,
input logic [5:0] resource_irq_hp_i,
input logic jpeg_quiesce_i,
input logic jpeg_reset_i,
axi4_if.slave cfg_axi4,
axi4_if.slave psram_axi4,
axi4_if.slave xpi_axi4,
@@ -44,6 +46,7 @@ module apb4_periph (
axi4_if.master sdio0_axi4,
axi4_if.master sdio1_axi4,
axi4_if.master usb2_axi4,
axi4_if.master jpeg_axi4,
sysctrl_if.dut sysctrl,
pll_ctrl_if.sysctrl pll_ctrl,
clock_ctrl_if.sysctrl clock_ctrl,
@@ -64,7 +67,8 @@ module apb4_periph (
output logic hp_software_irq_o,
output logic hp_machine_external_irq_o,
output logic hp_supervisor_external_irq_o,
output logic [4:0] resource_irq_raw_o,
output logic jpeg_idle_o,
output logic [5:0] resource_irq_raw_o,
output logic [`SOC_IRQ_APB4_PERIPH_WIDTH-1:0] irq_o
// verilog_format: on
);
@@ -147,6 +151,7 @@ axi4_stream_if #(
logic s_dma_crypto_in_proc;
logic s_dma_crypto_out_proc;
logic s_crypto_irq;
logic s_jpeg_irq_raw;
logic s_usb2_irq;
logic s_tim0_irq, s_tim1_irq;
logic s_dvp_irq;
@@ -204,7 +209,9 @@ axi4_stream_if #(
assign hp_software_irq_o = u_hp_clint_if.software_irq_o[1];
assign hp_machine_external_irq_o = s_hp_plic_context_irq[0];
assign hp_supervisor_external_irq_o = s_hp_plic_context_irq[1];
assign resource_irq_raw_o = {spisd.irq_o, sdio1.irq_o, sdio0.irq_o, s_usb2_irq, s_dma_irq};
assign resource_irq_raw_o = {
s_jpeg_irq_raw, spisd.irq_o, sdio1.irq_o, sdio0.irq_o, s_usb2_irq, s_dma_irq
};
apb4_async_bridge u_psram_cfg_mem_cdc (
.src_clk_i (clk_i),
@@ -283,6 +290,7 @@ axi4_stream_if #(
s_hp_plic_source[6] = resource_irq_hp_i[2];
s_hp_plic_source[7] = resource_irq_hp_i[3];
s_hp_plic_source[8] = resource_irq_hp_i[4];
s_hp_plic_source[9] = resource_irq_hp_i[5];
end
`include "apb4_periph_irq_bindings.svh"
@@ -450,6 +458,17 @@ axi4_stream_if #(
.crypto_out_axis (u_crypto_out_axis_if)
);
apb4_jpeg u_apb4_jpeg (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.quiesce_i (jpeg_quiesce_i),
.resource_reset_i(jpeg_reset_i),
.apb4 (u_jpeg_apb4_if),
.axi4 (jpeg_axi4),
.idle_o (jpeg_idle_o),
.irq_o (s_jpeg_irq_raw)
);
apb4_sysctrl u_apb4_sysctrl (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
+20 -18
View File
@@ -23,9 +23,9 @@ module apb4_system (
input logic rst_aud_n_i,
input logic debug_halted_i,
input logic ext_h_data_idle_i,
input logic [5:0] resource_idle_i,
input logic [5:0] resource_block_ack_i,
input logic [4:0] resource_irq_i,
input logic [6:0] resource_idle_i,
input logic [6:0] resource_block_ack_i,
input logic [5:0] resource_irq_i,
input logic cache_request_i,
axi4_if.slave axi4,
axi4_if.master ext_h_axi4,
@@ -45,11 +45,11 @@ module apb4_system (
output logic ext_h_irq_raw_o,
output logic [1:0] ext_h_owner_o,
output logic cache_clean_o,
output logic [5:0][1:0] resource_owner_o,
output logic [5:0] resource_quiesce_o,
output logic [5:0] resource_reset_o,
output logic [4:0] resource_irq_lp_o,
output logic [4:0] resource_irq_hp_o,
output logic [6:0][1:0] resource_owner_o,
output logic [6:0] resource_quiesce_o,
output logic [6:0] resource_reset_o,
output logic [5:0] resource_irq_lp_o,
output logic [5:0] resource_irq_hp_o,
output logic [`SOC_IRQ_APB4_SYSTEM_WIDTH-1:0] irq_o
// verilog_format: on
);
@@ -97,12 +97,12 @@ module apb4_system (
logic s_ext_h_irq;
logic s_ext_h_irq_raw;
logic [ 1:0] unused_ext_h_owner;
logic [ 5:0] s_resource_irq;
logic [ 5:0] s_resource_irq_lp;
logic [ 5:0] s_resource_irq_hp;
logic [ 5:0][1:0] s_resource_owner;
logic [ 5:0] s_resource_quiesce;
logic [ 5:0] s_resource_reset;
logic [ 6:0] s_resource_irq;
logic [ 6:0] s_resource_irq_lp;
logic [ 6:0] s_resource_irq_hp;
logic [ 6:0][1:0] s_resource_owner;
logic [ 6:0] s_resource_quiesce;
logic [ 6:0] s_resource_reset;
logic s_resource_fault_irq;
logic s_cache_clean;
logic s_unused_resource;
@@ -110,7 +110,7 @@ module apb4_system (
logic s_unused_product_input;
`endif
assign s_resource_irq = {s_ext_h_irq_raw, resource_irq_i};
assign s_resource_irq = {resource_irq_i[5], s_ext_h_irq_raw, resource_irq_i[4:0]};
assign s_ext_h_irq = s_resource_irq_lp[5];
assign ext_h_irq_raw_o = s_ext_h_irq_raw;
assign ext_h_owner_o = s_resource_owner[5];
@@ -118,8 +118,8 @@ module apb4_system (
assign resource_owner_o = s_resource_owner;
assign resource_quiesce_o = s_resource_quiesce;
assign resource_reset_o = s_resource_reset;
assign resource_irq_lp_o = s_resource_irq_lp[4:0];
assign resource_irq_hp_o = s_resource_irq_hp[4:0];
assign resource_irq_lp_o = {s_resource_irq_lp[6], s_resource_irq_lp[4:0]};
assign resource_irq_hp_o = {s_resource_irq_hp[6], s_resource_irq_hp[4:0]};
`ifdef PDK_IHP130
localparam logic [31:0] ARCHINFO_TECHNOLOGY = 32'h0201_0082;
@@ -230,7 +230,9 @@ module apb4_system (
apb4_crc u_apb4_crc (.apb4(u_crc_apb4_if));
resource_controller u_resource_controller (
resource_controller #(
.ResourceCount(7)
) u_resource_controller (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.idle_i (resource_idle_i),
+28 -13
View File
@@ -85,6 +85,8 @@ module retrosoc (
u_opipsram_axi4_if (.aclk(clk_lp_i), .aresetn(rst_lp_n_i));
axi4_if #(.ADDR_WIDTH(32), .DATA_WIDTH(64), .ID_WIDTH(3), .USER_WIDTH(1))
u_ext_h_wide_axi4_if (.aclk(clk_pclk_i), .aresetn(rst_pclk_n_i));
axi4_if #(.ADDR_WIDTH(32), .DATA_WIDTH(64), .ID_WIDTH(3), .USER_WIDTH(1))
u_jpeg_wide_axi4_if (.aclk(clk_pclk_i), .aresetn(rst_pclk_n_i));
axi4_if #(.ADDR_WIDTH(32), .DATA_WIDTH(64), .ID_WIDTH(3), .USER_WIDTH(1))
u_hp_icache_axi4_if (.aclk(clk_hp_i), .aresetn(rst_hp_n_i));
axi4_if #(.ADDR_WIDTH(32), .DATA_WIDTH(64), .ID_WIDTH(3), .USER_WIDTH(1))
@@ -230,16 +232,18 @@ module retrosoc (
logic [31:0] s_ext_h_timeout;
logic s_ext_h_irq_raw;
logic [ 1:0] s_ext_h_owner;
logic [ 5:0][1:0] s_resource_owner;
logic [ 5:0] s_resource_quiesce;
logic [ 5:0] s_resource_reset;
logic [ 5:0] s_resource_idle_hp;
logic [ 5:0] s_resource_idle_pclk;
logic [ 5:0] s_resource_block_ack_hp;
logic [ 5:0] s_resource_block_ack_pclk;
logic [ 4:0] s_resource_irq_raw;
logic [ 4:0] s_resource_irq_lp;
logic [ 4:0] s_resource_irq_hp;
logic [ 6:0][1:0] s_resource_owner;
logic [ 6:0] s_resource_quiesce;
logic [ 6:0] s_resource_reset;
logic [ 6:0] s_resource_idle_hp;
logic [ 6:0] s_resource_idle_pclk;
logic [ 6:0] s_resource_block_ack_hp;
logic [ 6:0] s_resource_block_ack_pclk;
logic [ 5:0] s_resource_irq_raw;
logic [ 5:0] s_resource_irq_lp;
logic [ 5:0] s_resource_irq_hp;
logic s_jpeg_idle;
logic [ 6:0] s_resource_idle_combined;
logic [31:0] s_fault_addr_mux;
logic [ 3:0] s_fault_wstrb_mux;
logic s_fault_reserved_mux;
@@ -565,6 +569,7 @@ core_wrapper u_core_wrapper (
.sdio1_axi4 (u_sdio1_axi4_if),
.spisd_axi4 (u_spisd_axi4_if),
.usb2_axi4 (u_usb2_axi4_if),
.jpeg_axi4 (u_jpeg_wide_axi4_if),
.lp_data_axi4 (u_mgmt_data_axi4_if),
.ext_h_axi4 (u_ext_h_wide_axi4_if),
.sram_gateway_axi4 (u_data_sram_axi4_if),
@@ -590,7 +595,7 @@ core_wrapper u_core_wrapper (
cdc_sync #(
.STAGE (2),
.DATA_WIDTH(6)
.DATA_WIDTH(7)
) u_resource_idle_sync (
.clk_i (clk_pclk_i),
.rst_n_i(rst_pclk_n_i),
@@ -599,7 +604,7 @@ core_wrapper u_core_wrapper (
);
cdc_sync #(
.STAGE (2),
.DATA_WIDTH(6)
.DATA_WIDTH(7)
) u_resource_block_ack_sync (
.clk_i (clk_pclk_i),
.rst_n_i(rst_pclk_n_i),
@@ -792,11 +797,14 @@ core_wrapper u_core_wrapper (
.ext_h_hp_irq_i ((s_ext_h_owner == 2'd1) ? s_ext_h_irq_raw : 1'b0),
.resource_irq_lp_i (s_resource_irq_lp),
.resource_irq_hp_i (s_resource_irq_hp),
.jpeg_quiesce_i (s_resource_quiesce[6]),
.jpeg_reset_i (s_resource_reset[6]),
.cfg_axi4 (u_cfg_pclk_axi4_if),
.dma_axi4 (u_dma_axi4_if),
.sdio0_axi4 (u_sdio0_axi4_if),
.sdio1_axi4 (u_sdio1_axi4_if),
.usb2_axi4 (u_usb2_axi4_if),
.jpeg_axi4 (u_jpeg_wide_axi4_if),
.psram_axi4 (u_data_qpi_axi4_if),
.xpi_axi4 (u_data_xpi_axi4_if),
.spisd_axi4 (u_spisd_axi4_if),
@@ -832,6 +840,7 @@ core_wrapper u_core_wrapper (
.hp_machine_external_irq_o (s_hp_machine_external_irq),
.hp_supervisor_external_irq_o(s_hp_supervisor_external_irq),
.resource_irq_raw_o (s_resource_irq_raw),
.jpeg_idle_o (s_jpeg_idle),
.irq_o (s_apb4_periph_irq)
);
@@ -850,7 +859,7 @@ core_wrapper u_core_wrapper (
.rst_aud_n_i(rst_aud_n_i),
.debug_halted_i(s_mgmt_debug_halted),
.ext_h_data_idle_i(s_ext_h_data_idle),
.resource_idle_i({s_ext_h_data_idle && s_resource_idle_pclk[5], s_resource_idle_pclk[4:0]}),
.resource_idle_i(s_resource_idle_combined),
.resource_block_ack_i(s_resource_block_ack_pclk),
.resource_irq_i(s_resource_irq_raw),
.cache_request_i(s_hp_cache_req_pclk),
@@ -880,6 +889,12 @@ core_wrapper u_core_wrapper (
.irq_o(s_apb4_system_irq)
);
assign s_resource_idle_combined = {
s_jpeg_idle && s_resource_idle_pclk[6],
s_ext_h_data_idle && s_resource_idle_pclk[5],
s_resource_idle_pclk[4:0]
};
logic [35:0] s_unused_domain_inputs;
assign s_unused_domain_inputs = {
clk_pclk_i,
+58 -9
View File
@@ -16,7 +16,7 @@ module soc_data_plane (
input logic block_new_i,
input logic recovery_i,
input logic flush_i,
input logic [5:0] resource_block_i,
input logic [6:0] resource_block_i,
input logic [1:0] mem_pad_mode_i,
input logic ext_h_block_i,
input logic [31:0] ext_h_read_base_i,
@@ -30,6 +30,7 @@ module soc_data_plane (
axi4_if.slave sdio1_axi4,
axi4_if.slave spisd_axi4,
axi4_if.slave usb2_axi4,
axi4_if.slave jpeg_axi4,
axi4_if.slave lp_data_axi4,
axi4_if.slave ext_h_axi4,
axi4_if.master sram_gateway_axi4,
@@ -41,8 +42,8 @@ module soc_data_plane (
output logic idle_o,
output logic flush_busy_o,
output logic ext_h_idle_o,
output logic [5:0] resource_idle_o,
output logic [5:0] resource_block_ack_o,
output logic [6:0] resource_idle_o,
output logic [6:0] resource_block_ack_o,
output logic [7:0] outstanding_read_o,
output logic [7:0] outstanding_write_o,
output logic fault_valid_o,
@@ -78,12 +79,14 @@ module soc_data_plane (
logic [ 3:0] s_crossbar_fault_reason;
logic s_lp_clear_busy;
logic s_ext_clear_busy;
logic s_jpeg_clear_busy;
logic [ NumIoMasters-1:0][ 7:0] unused_io_epoch;
logic [NumMemoryTargets-1:0][ 7:0] unused_target_epoch;
logic [ 7:0] unused_lp_epoch;
logic [ 7:0] unused_ext_epoch;
logic [ 7:0] unused_jpeg_epoch;
logic [ 2:0] s_unused_epoch;
logic [ 5:0] s_resource_block_hp;
logic [ 6:0] s_resource_block_hp;
logic [ 7:0] s_master_idle;
logic [ 7:0] s_master_block;
logic [ 7:0] s_monitor_master_read_accept;
@@ -230,6 +233,24 @@ module soc_data_plane (
.aclk (clk_hp_i),
.aresetn(rst_hp_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(64),
.ID_WIDTH (3),
.USER_WIDTH(1)
) u_jpeg_hp_axi4 (
.aclk (clk_hp_i),
.aresetn(rst_hp_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(64),
.ID_WIDTH (6),
.USER_WIDTH(1)
) u_jpeg_prefixed_axi4 (
.aclk (clk_hp_i),
.aresetn(rst_hp_n_i)
);
cdc_sync #(
.STAGE (2),
.DATA_WIDTH(2)
@@ -241,7 +262,7 @@ module soc_data_plane (
);
cdc_sync #(
.STAGE (2),
.DATA_WIDTH(6)
.DATA_WIDTH(7)
) u_resource_block_sync (
.clk_i (clk_hp_i),
.rst_n_i(rst_hp_n_i),
@@ -251,13 +272,15 @@ module soc_data_plane (
assign s_master_block = {
s_resource_block_hp[5],
2'b00,
s_resource_block_hp[6],
1'b0,
s_resource_block_hp[4] || s_resource_block_hp[3],
s_resource_block_hp[2] || s_resource_block_hp[1],
s_resource_block_hp[0],
2'b00
};
assign resource_idle_o = {
s_master_idle[6],
s_master_idle[7],
s_master_idle[4],
s_master_idle[4],
@@ -358,7 +381,30 @@ module soc_data_plane (
.narrow (u_lp_data_hp_axi4),
.wide (u_master_axi4[5])
);
axi4_master_idle u_reserved_master_idle (.axi4(u_master_axi4[6]));
axi4_async_bridge #(
.DataWidth(64),
.IdWidth (3)
) u_jpeg_cdc (
.src_clk_i (clk_io_i),
.src_rst_n_i (rst_io_n_i),
.dst_clk_i (clk_hp_i),
.dst_rst_n_i (rst_hp_n_i),
.clear_i (flush_i),
.clear_busy_o(s_jpeg_clear_busy),
.epoch_o (unused_jpeg_epoch),
.src_axi4 (jpeg_axi4),
.dst_axi4 (u_jpeg_hp_axi4)
);
axi4_id_prefix #(
.MasterIndex(3'd6)
) u_jpeg_prefix (
.source(u_jpeg_hp_axi4),
.sink (u_jpeg_prefixed_axi4)
);
axi4_connector u_jpeg_connector (
.source(u_jpeg_prefixed_axi4),
.sink (u_master_axi4[6])
);
axi4_async_bridge #(
.DataWidth(64),
@@ -590,9 +636,12 @@ module soc_data_plane (
end
assign flush_busy_o = (|s_io_clear_busy) || (|s_target_clear_busy) ||
(|s_guard_clear_busy) || s_lp_clear_busy || s_ext_clear_busy;
(|s_guard_clear_busy) || s_lp_clear_busy || s_ext_clear_busy ||
s_jpeg_clear_busy;
assign s_unused_epoch = {
^unused_io_epoch, ^unused_target_epoch, ^{unused_lp_epoch, unused_ext_epoch}
^unused_io_epoch,
^unused_target_epoch,
^{unused_lp_epoch, unused_ext_epoch, unused_jpeg_epoch}
};
axi4_connector u_qpi_gateway_connector (
+24 -1
View File
@@ -133,10 +133,33 @@
"sdram_reg.sv"
],
"rtl/ip/multimedia": [
"apb4_jpeg.sv",
"axi4s_dvp.sv",
"dvp_core.sv",
"dvp_define.svh",
"dvp_reg.sv"
"dvp_reg.sv",
"jpeg_bit_packer.sv",
"jpeg_bit_reader.sv",
"jpeg_block_decoder.sv",
"jpeg_block_encoder.sv",
"jpeg_byte_joiner.sv",
"jpeg_byte_unpacker.sv",
"jpeg_decode_core.sv",
"jpeg_define.svh",
"jpeg_dma.sv",
"jpeg_encode_core.sv",
"jpeg_entropy_decoder.sv",
"jpeg_entropy_encoder.sv",
"jpeg_header_writer.sv",
"jpeg_marker_parser.sv",
"jpeg_mcu_builder.sv",
"jpeg_mcu_reconstructor.sv",
"jpeg_pkg.sv",
"jpeg_quantizer.sv",
"jpeg_reg.sv",
"jpeg_table_cache.sv",
"jpeg_table_store.sv",
"jpeg_transform.sv"
],
"rtl/ip/peripheral": [
"apb4_clint.sv",
+1
View File
@@ -32,6 +32,7 @@ TEST_SOURCES = (
"crt/src/hal/spisd_math.c",
"crt/src/hal/i2s_math.c",
"crt/src/hal/dma_math.c",
"crt/src/hal/jpeg_math.c",
"crt/src/hal/sysctrl.c",
"crt/src/hal/clock.c",
"crt/src/hal/resource.c",
+64
View File
@@ -13,6 +13,7 @@
#include <retrosoc/hal/i2c.h>
#include <retrosoc/hal/lcd.h>
#include <retrosoc/hal/i2s.h>
#include <retrosoc/hal/jpeg.h>
#include <retrosoc/hal/psram.h>
#include <retrosoc/hal/resource.h>
#include <retrosoc/hal/sdram.h>
@@ -863,6 +864,68 @@ static int test_resource_validation(void) {
return 0;
}
static int test_jpeg_validation(void) {
static _Alignas(RS_JPEG_DESCRIPTOR_ALIGNMENT) rs_jpeg_descriptor_t ring[2];
rs_jpeg_descriptor_t descriptor;
rs_jpeg_job_t job = {
.mode = RS_JPEG_MODE_ENCODE,
.input_format = RS_JPEG_FORMAT_RGB565,
.output_format = RS_JPEG_FORMAT_RGB565,
.sampling = RS_JPEG_SAMPLING_420,
.width = 1920U,
.height = 1080U,
.quality = 75U,
.table_context = 0U,
.restart_interval = 64U,
.bitstream = (uintptr_t)UINT32_C(0x38000000),
.bitstream_size = UINT32_C(0x00100000),
.planes = {(uintptr_t)UINT32_C(0x38100000), (uintptr_t)0U, (uintptr_t)0U},
.strides = {3840U, 0U, 0U},
.metadata = (uintptr_t)0U,
.metadata_length = 0U,
.auto_header = true,
.strict = true,
};
if ((rs_jpeg_job_validate(&job) != RS_OK) ||
(rs_jpeg_descriptor_build(&descriptor, &job, UINT64_C(0x123456789ABCDEF0), true) !=
RS_OK) ||
(descriptor.image_size != UINT32_C(0x04380780)) ||
(descriptor.cookie_lo != UINT32_C(0x9ABCDEF0)) ||
(descriptor.cookie_hi != UINT32_C(0x12345678)) ||
((descriptor.control &
(RS_JPEG_DESCRIPTOR_OWN | RS_JPEG_DESCRIPTOR_IOC | RS_JPEG_DESCRIPTOR_ENCODE)) !=
(RS_JPEG_DESCRIPTOR_OWN | RS_JPEG_DESCRIPTOR_IOC | RS_JPEG_DESCRIPTOR_ENCODE))) {
return 1;
}
if ((rs_jpeg_ring_validate(ring, 2U) != RS_OK) ||
(rs_jpeg_ring_validate(ring, 3U) != RS_EINVAL) ||
(rs_jpeg_ring_validate(NULL, 2U) != RS_EINVAL)) {
return 2;
}
job.quality = 0U;
if (rs_jpeg_job_validate(&job) != RS_EINVAL) {
return 3;
}
job.quality = 75U;
job.strides[0] = 3839U;
if (rs_jpeg_job_validate(&job) != RS_EINVAL) {
return 4;
}
job.strides[0] = 3840U;
job.bitstream += 1U;
if (rs_jpeg_job_validate(&job) != RS_EINVAL) {
return 5;
}
job.bitstream -= 1U;
job.metadata = (uintptr_t)UINT32_C(0x38200000);
job.metadata_length = 8U;
if (rs_jpeg_job_validate(&job) != RS_EINVAL) {
return 6;
}
return 0;
}
static int test_ps2_decoders(void) {
ps2_keyboard_decoder_t keyboard;
ps2_mouse_decoder_t mouse;
@@ -953,6 +1016,7 @@ int main(void) {
test_user_ip_validation(),
test_extension_validation(),
test_resource_validation(),
test_jpeg_validation(),
test_ps2_decoders(),
test_wav_parser(),
test_video_parser(),
File diff suppressed because it is too large. Load diff
+182
View File
@@ -0,0 +1,182 @@
`timescale 1ns / 1ps
`include "axi4_define.svh"
module apb4_jpeg_ring_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic [63:0] memory [0:2047];
logic read_active;
logic [31:0] read_base;
logic [ 7:0] read_len;
logic [ 7:0] read_index;
logic write_active;
logic [31:0] write_base;
logic [ 7:0] write_index;
logic bvalid;
logic irq;
apb4_if apb4 (
.pclk (clk),
.presetn(rst_n)
);
axi4_if #(
.DATA_WIDTH(64),
.ID_WIDTH (3)
) axi4 (
.aclk (clk),
.aresetn(rst_n)
);
always #5 clk = ~clk;
apb4_jpeg u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.quiesce_i (1'b0),
.resource_reset_i(1'b0),
.apb4 (apb4),
.axi4 (axi4),
.idle_o (),
.irq_o (irq)
);
assign axi4.arready = !read_active;
assign axi4.rid = 3'd0;
assign axi4.rdata = memory[(read_base>>3)+read_index];
assign axi4.rresp = `AXI4_RESP_OKAY;
assign axi4.rlast = read_index == read_len;
assign axi4.ruser = '0;
assign axi4.rvalid = read_active;
assign axi4.awready = !write_active && !bvalid;
assign axi4.wready = write_active;
assign axi4.bid = 3'd0;
assign axi4.bresp = `AXI4_RESP_OKAY;
assign axi4.buser = '0;
assign axi4.bvalid = bvalid;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
read_active <= 1'b0;
read_base <= '0;
read_len <= '0;
read_index <= '0;
write_active <= 1'b0;
write_base <= '0;
write_index <= '0;
bvalid <= 1'b0;
end else begin
if (axi4.arvalid && axi4.arready) begin
read_active <= 1'b1;
read_base <= axi4.araddr;
read_len <= axi4.arlen;
read_index <= 8'd0;
end else if (axi4.rvalid && axi4.rready) begin
if (axi4.rlast) begin
read_active <= 1'b0;
end else begin
read_index <= read_index + 1'b1;
end
end
if (axi4.awvalid && axi4.awready) begin
write_active <= 1'b1;
write_base <= axi4.awaddr;
write_index <= 8'd0;
end
if (axi4.wvalid && axi4.wready) begin
for (int byte_index = 0; byte_index < 8; byte_index++) begin
if (axi4.wstrb[byte_index]) begin
memory[(write_base>>3)+write_index][byte_index*8+:8] <= axi4.wdata[byte_index*8+:8];
end
end
if (axi4.wlast) begin
write_active <= 1'b0;
bvalid <= 1'b1;
end else begin
write_index <= write_index + 1'b1;
end
end
if (axi4.bvalid && axi4.bready) begin
bvalid <= 1'b0;
end
end
end
task automatic apb_write(input logic [11:0] offset_i, input logic [31:0] data_i);
begin
@(negedge clk);
apb4.paddr = {20'd0, offset_i};
apb4.pwdata = data_i;
apb4.pstrb = 4'hf;
apb4.pwrite = 1'b1;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk);
apb4.penable = 1'b1;
#1;
if (!apb4.pready || apb4.pslverr) $fatal(1, "APB write failed at %h", offset_i);
@(negedge clk);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
task automatic apb_read(input logic [11:0] offset_i, output logic [31:0] data_o);
begin
@(negedge clk);
apb4.paddr = {20'd0, offset_i};
apb4.pwrite = 1'b0;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk);
apb4.penable = 1'b1;
#1;
if (!apb4.pready || apb4.pslverr) $fatal(1, "APB read failed at %h", offset_i);
data_o = apb4.prdata;
@(negedge clk);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
initial begin
logic [31:0] value;
int timeout;
for (int index = 0; index < 2048; index++) begin
memory[index] = 64'd0;
end
memory[32'h1000>>3] = 64'h0000000000000001;
apb4.paddr = '0;
apb4.pprot = '0;
apb4.psel = 1'b0;
apb4.penable = 1'b0;
apb4.pwrite = 1'b0;
apb4.pwdata = '0;
apb4.pstrb = '0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
apb_write(12'h01c, 32'h12);
apb_write(12'h100, 32'h00001000);
apb_write(12'h104, 32'd2);
apb_write(12'h10c, 32'd1);
apb_write(12'h110, 32'd1);
apb_write(12'h11c, 32'd1);
timeout = 2000;
value = 32'd1;
while ((value[0] != 1'b0) && (timeout > 0)) begin
apb_read(12'h014, value);
timeout--;
end
if (timeout == 0) $fatal(1, "ring job did not complete");
apb_read(12'h108, value);
if (value != 32'd1) $fatal(1, "ring head was not advanced");
apb_read(12'h018, value);
if ((value & 32'h12) != 32'h12 || !irq) $fatal(1, "ring IRQ status mismatch");
if (memory[32'h1000>>3][31:0] != 32'd0 || memory[32'h1000>>3][63:32] != 32'h00000103) begin
$fatal(1, "descriptor error writeback mismatch: %h", memory[32'h1000>>3]);
end
$display("JPEG APB ring tests passed");
$finish;
end
endmodule
+89
View File
@@ -0,0 +1,89 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module jpeg_bit_packer_tb;
logic clk;
logic rst_n;
logic [127:0] token_bits;
logic [ 23:0] token_length;
logic [ 2:0] token_count;
logic token_valid;
logic token_ready;
logic flush;
logic flush_ready;
logic flush_done;
logic [ 63:0] byte_data;
logic [ 7:0] byte_keep;
logic byte_valid;
logic byte_last;
logic byte_ready;
logic error_flag;
jpeg_bit_packer u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.token_bits_i (token_bits),
.token_length_i(token_length),
.token_count_i (token_count),
.token_valid_i (token_valid),
.token_ready_o (token_ready),
.flush_i (flush),
.flush_ready_o (flush_ready),
.flush_done_o (flush_done),
.byte_data_o (byte_data),
.byte_keep_o (byte_keep),
.byte_valid_o (byte_valid),
.byte_last_o (byte_last),
.byte_ready_i (byte_ready),
.error_o (error_flag)
);
always #5 clk = ~clk;
initial begin
clk = 1'b0;
rst_n = 1'b0;
token_bits = '0;
token_length = '0;
token_count = 3'd0;
token_valid = 1'b0;
flush = 1'b0;
byte_ready = 1'b0;
repeat (3) @(negedge clk);
rst_n = 1'b1;
token_bits[0+:32] = 32'hff;
token_length[0+:6] = 6'd8;
token_bits[32+:32] = 32'ha;
token_length[6+:6] = 6'd4;
token_count = 3'd2;
token_valid = 1'b1;
@(negedge clk);
if (!token_ready) $fatal(1, "token input was not accepted");
token_valid = 1'b0;
while (!flush_ready) @(negedge clk);
flush = 1'b1;
@(negedge clk);
flush = 1'b0;
while (!byte_valid) @(negedge clk);
repeat (2) begin
@(negedge clk);
if (byte_data[23:0] != 24'haf00ff || byte_keep != 8'h07 || !byte_last) begin
$fatal(1, "stuffed output changed under backpressure");
end
end
byte_ready = 1'b1;
#1;
if (!flush_done || error_flag) $fatal(1, "flush completion mismatch");
@(negedge clk);
byte_ready = 1'b0;
if (!flush_ready) $fatal(1, "packer did not return to running state");
flush = 1'b1;
@(negedge clk);
flush = 1'b0;
if (!flush_done || byte_valid) $fatal(1, "empty flush completion mismatch");
@(negedge clk);
$display("JPEG bit packer tests passed");
$finish;
end
endmodule
+111
View File
@@ -0,0 +1,111 @@
`timescale 1ns / 1ps
module jpeg_bit_reader_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic [ 7:0] byte_data;
logic byte_valid;
logic byte_ready;
logic byte_last;
logic [ 5:0] bit_consume;
logic bit_consume_valid;
logic align_bits;
logic [31:0] bit_window;
logic [ 5:0] bit_count;
logic marker_valid;
logic [ 7:0] marker;
logic marker_ready;
logic unpack_error;
logic reader_error;
axi4_stream_if #(
.DATA_WIDTH(64)
) input_axis (
.aclk (clk),
.aresetn(rst_n)
);
always #5 clk = ~clk;
jpeg_byte_unpacker u_unpacker (
.clk_i (clk),
.rst_n_i (rst_n),
.input_axis (input_axis),
.byte_o (byte_data),
.byte_valid_o(byte_valid),
.byte_ready_i(byte_ready),
.byte_last_o (byte_last),
.error_o (unpack_error)
);
jpeg_bit_reader u_reader (
.clk_i (clk),
.rst_n_i (rst_n),
.byte_i (byte_data),
.byte_valid_i (byte_valid),
.byte_ready_o (byte_ready),
.byte_last_i (byte_last),
.bit_consume_i (bit_consume),
.bit_consume_valid_i(bit_consume_valid),
.align_i (align_bits),
.bit_window_o (bit_window),
.bit_count_o (bit_count),
.marker_valid_o (marker_valid),
.marker_o (marker),
.marker_ready_i (marker_ready),
.error_o (reader_error)
);
initial begin
input_axis.tdata = 64'hd9ff7fd0ffaf00ff;
input_axis.tkeep = 8'hff;
input_axis.tstrb = 8'hff;
input_axis.tlast = 1'b1;
input_axis.tid = '0;
input_axis.tdest = '0;
input_axis.tuser = '0;
input_axis.tvalid = 1'b0;
bit_consume = 6'd0;
bit_consume_valid = 1'b0;
align_bits = 1'b0;
marker_ready = 1'b0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
@(negedge clk);
input_axis.tvalid = 1'b1;
#1;
if (!input_axis.tready) $fatal(1, "input AXIS beat was not accepted");
@(negedge clk);
input_axis.tvalid = 1'b0;
while (!marker_valid) @(negedge clk);
if (marker != 8'hd0 || bit_count != 6'd16 || bit_window[31:16] != 16'hffaf) begin
$fatal(1, "restart boundary mismatch: marker=%h count=%0d window=%h", marker, bit_count,
bit_window);
end
bit_consume = 6'd12;
bit_consume_valid = 1'b1;
@(negedge clk);
bit_consume_valid = 1'b0;
if (bit_count != 6'd4 || bit_window[31:28] != 4'hf) begin
$fatal(1, "bit consumption mismatch");
end
align_bits = 1'b1;
marker_ready = 1'b1;
@(negedge clk);
align_bits = 1'b0;
marker_ready = 1'b0;
while (!marker_valid) @(negedge clk);
if (marker != 8'hd9 || bit_count != 6'd8 || bit_window[31:24] != 8'h7f) begin
$fatal(1, "EOI boundary mismatch: marker=%h count=%0d window=%h", marker, bit_count,
bit_window);
end
align_bits = 1'b1;
marker_ready = 1'b1;
@(negedge clk);
if (unpack_error || reader_error) $fatal(1, "unexpected byte/bit reader error");
$display("JPEG byte unpacker and bit reader tests passed");
$finish;
end
endmodule
+93
View File
@@ -0,0 +1,93 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module jpeg_block_decoder_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic start;
logic [ 511:0] quant;
logic [1599:0] reciprocal;
logic [ 191:0] dc_code;
logic [ 59:0] dc_length;
logic [4095:0] ac_code;
logic [1279:0] ac_length;
logic [ 31:0] bit_window;
logic [ 5:0] bit_count;
logic [ 5:0] bit_consume;
logic bit_consume_valid;
logic [ 511:0] block_out;
logic signed [ 23:0] dc_out;
logic result_valid;
logic result_ready;
logic error_flag;
always #5 clk = ~clk;
jpeg_block_decoder u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.previous_dc_i (24'sd2),
.quant_i (quant),
.reciprocal_i (reciprocal),
.dc_code_i (dc_code),
.dc_length_i (dc_length),
.ac_code_i (ac_code),
.ac_length_i (ac_length),
.bit_window_i (bit_window),
.bit_count_i (bit_count),
.bit_consume_o (bit_consume),
.bit_consume_valid_o(bit_consume_valid),
.start_ready_o (),
.block_o (block_out),
.dc_o (dc_out),
.result_valid_o (result_valid),
.result_ready_i (result_ready),
.error_o (error_flag)
);
always @(posedge clk) begin
if (rst_n && bit_consume_valid) begin
bit_window <= bit_window << bit_consume;
bit_count <= bit_count - bit_consume;
end
end
initial begin
start = 1'b0;
quant = {64{8'd1}};
reciprocal = {64{25'd16777216}};
dc_code = '0;
dc_length = '0;
ac_code = '0;
ac_length = '0;
bit_window = {23'b10111_1110_1110_1110_101011, 9'd0};
bit_count = 6'd23;
result_ready = 1'b0;
dc_code[2*16+:16] = 16'b101;
dc_length[2*5+:5] = 5'd3;
ac_code[8'hf0*16+:16] = 16'b1110;
ac_length[8'hf0*5+:5] = 5'd4;
ac_code[8'he1*16+:16] = 16'b10101;
ac_length[8'he1*5+:5] = 5'd5;
repeat (3) @(posedge clk);
rst_n = 1'b1;
@(negedge clk);
start = 1'b1;
@(negedge clk);
start = 1'b0;
while (!result_valid) @(negedge clk);
if (error_flag || dc_out != 24'sd5 || bit_count != 6'd0) begin
$fatal(1, "block decode status mismatch");
end
for (int index = 0; index < 64; index++) begin
if (block_out[index*8+:8] != 8'd129) begin
$fatal(1, "decoded sample mismatch at %0d: %0d", index, block_out[index*8+:8]);
end
end
result_ready = 1'b1;
@(negedge clk);
$display("JPEG block decoder tests passed");
$finish;
end
endmodule
+104
View File
@@ -0,0 +1,104 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module jpeg_block_encoder_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic start;
logic [ 511:0] block_in;
logic [ 511:0] quant;
logic [1599:0] reciprocal;
logic [ 191:0] dc_code;
logic [ 59:0] dc_length;
logic [4095:0] ac_code;
logic [1279:0] ac_length;
logic [ 127:0] token_bits;
logic [ 23:0] token_length;
logic [ 2:0] token_count;
logic token_valid;
logic token_last;
logic signed [ 23:0] dc_out;
logic result_valid;
logic result_ready;
logic error_flag;
int token_index;
always #5 clk = ~clk;
jpeg_block_encoder u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.block_i (block_in),
.previous_dc_i (24'sd0),
.quant_i (quant),
.reciprocal_i (reciprocal),
.dc_code_i (dc_code),
.dc_length_i (dc_length),
.ac_code_i (ac_code),
.ac_length_i (ac_length),
.start_ready_o (),
.token_bits_o (token_bits),
.token_length_o(token_length),
.token_count_o (token_count),
.token_valid_o (token_valid),
.token_last_o (token_last),
.token_ready_i (1'b1),
.dc_o (dc_out),
.result_valid_o(result_valid),
.result_ready_i(result_ready),
.error_o (error_flag)
);
always @(posedge clk) begin
if (rst_n && token_valid) begin
if (token_index == 0) begin
if (token_count != 3'd1 || token_bits[0+:32] != 32'd88 ||
token_length[0+:6] != 6'd7 || token_last) begin
$fatal(1, "DC token mismatch");
end
end else if (token_index == 1) begin
if (token_count != 3'd1 || token_bits[0+:32] != 32'd3 ||
token_length[0+:6] != 6'd2 || !token_last) begin
$fatal(1, "EOB token mismatch: count=%0d bits=%0d len=%0d last=%0d", token_count,
token_bits[0+:32], token_length[0+:6], token_last);
end
end else begin
$fatal(1, "unexpected token beat");
end
token_index <= token_index + 1;
end
end
initial begin
start = 1'b0;
block_in = {64{8'd129}};
quant = {64{8'd1}};
reciprocal = {64{25'd16777216}};
dc_code = '0;
dc_length = '0;
ac_code = '0;
ac_length = '0;
dc_code[4*16+:16] = 16'b101;
dc_length[4*5+:5] = 5'd3;
ac_code[0+:16] = 16'b11;
ac_length[0+:5] = 5'd2;
result_ready = 1'b0;
token_index = 0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
@(negedge clk);
start = 1'b1;
@(negedge clk);
start = 1'b0;
while (!result_valid) @(negedge clk);
if (error_flag || dc_out != 24'sd8 || token_index != 2) begin
$fatal(1, "block encode status mismatch: err=%0d dc=%0d tokens=%0d", error_flag, dc_out,
token_index);
end
result_ready = 1'b1;
@(negedge clk);
$display("JPEG block encoder tests passed");
$finish;
end
endmodule
+91
View File
@@ -0,0 +1,91 @@
`timescale 1ns / 1ps
module jpeg_byte_joiner_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic [63:0] input_data;
logic [ 7:0] input_keep;
logic input_valid;
logic input_ready;
logic input_last;
logic error_flag;
int output_beats;
axi4_stream_if #(
.DATA_WIDTH(64)
) output_axis (
.aclk (clk),
.aresetn(rst_n)
);
always #5 clk = ~clk;
jpeg_byte_joiner u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.input_data_i (input_data),
.input_keep_i (input_keep),
.input_valid_i(input_valid),
.input_ready_o(input_ready),
.input_last_i (input_last),
.output_axis (output_axis),
.error_o (error_flag)
);
task automatic send_beat(input logic [63:0] data_i, input logic [7:0] keep_i, input logic last_i);
begin
@(negedge clk);
input_data = data_i;
input_keep = keep_i;
input_last = last_i;
input_valid = 1'b1;
@(posedge clk);
while (!input_ready) @(posedge clk);
@(negedge clk);
input_valid = 1'b0;
end
endtask
always @(posedge clk) begin
if (rst_n && output_axis.tvalid && output_axis.tready) begin
if (output_beats == 0) begin
if (output_axis.tdata != 64'ha2a1a00504030201 || output_axis.tkeep != 8'hff ||
output_axis.tlast) begin
$fatal(1, "joined full beat mismatch");
end
end else if (output_beats == 1) begin
if (output_axis.tdata[15:0] != 16'hd9ff || output_axis.tkeep != 8'h03 ||
!output_axis.tlast) begin
$fatal(1, "joined final beat mismatch");
end
end else begin
$fatal(1, "unexpected joined output beat");
end
output_beats <= output_beats + 1;
end
end
initial begin
input_data = '0;
input_keep = '0;
input_valid = 1'b0;
input_last = 1'b0;
output_axis.tready = 1'b0;
output_beats = 0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
send_beat(64'h0000000504030201, 8'h1f, 1'b0);
send_beat(64'h0000000000a2a1a0, 8'h07, 1'b0);
repeat (2) @(negedge clk);
if (!output_axis.tvalid || output_axis.tdata != 64'ha2a1a00504030201) begin
$fatal(1, "full output was not held under backpressure: valid=%0d data=%h keep=%h",
output_axis.tvalid, output_axis.tdata, output_axis.tkeep);
end
output_axis.tready = 1'b1;
send_beat(64'h000000000000d9ff, 8'h03, 1'b1);
while (output_beats < 2) @(negedge clk);
if (error_flag) $fatal(1, "unexpected byte joiner error");
$display("JPEG byte joiner tests passed");
$finish;
end
endmodule
+139
View File
@@ -0,0 +1,139 @@
`timescale 1ns / 1ps
module jpeg_decode_core_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic start;
logic busy;
logic done;
logic [15:0] width;
logic [15:0] height;
logic [ 1:0] sampling;
logic error_flag;
logic [ 4:0] error_code;
logic [ 7:0] input_bytes [0:2047];
logic [ 7:0] expected_rgb [0:4095];
integer input_size;
integer expected_size;
integer output_index;
string input_path;
string expected_path;
axi4_stream_if #(
.DATA_WIDTH(64)
) bitstream_axis (
.aclk (clk),
.aresetn(rst_n)
);
axi4_stream_if #(
.DATA_WIDTH(64)
) pixel_axis (
.aclk (clk),
.aresetn(rst_n)
);
always #5 clk = ~clk;
jpeg_decode_core u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.output_format_i(3'd2),
.bitstream_axis (bitstream_axis),
.pixel_axis (pixel_axis),
.start_ready_o (),
.busy_o (busy),
.done_o (done),
.width_o (width),
.height_o (height),
.sampling_o (sampling),
.error_o (error_flag),
.error_code_o (error_code)
);
task automatic send_input_beat(input integer offset_i);
logic [63:0] data;
logic [ 7:0] keep;
begin
data = '0;
keep = '0;
for (int lane = 0; lane < 8; lane++) begin
if ((offset_i + lane) < input_size) begin
data[lane*8+:8] = input_bytes[offset_i+lane];
keep[lane] = 1'b1;
end
end
@(negedge clk);
bitstream_axis.tdata = data;
bitstream_axis.tkeep = keep;
bitstream_axis.tstrb = keep;
bitstream_axis.tlast = (offset_i + 8) >= input_size;
bitstream_axis.tvalid = 1'b1;
@(posedge clk);
while (!bitstream_axis.tready) @(posedge clk);
@(negedge clk);
bitstream_axis.tvalid = 1'b0;
end
endtask
always @(posedge clk) begin
if (rst_n && pixel_axis.tvalid && pixel_axis.tready) begin
for (int lane = 0; lane < 8; lane++) begin
if (pixel_axis.tkeep[lane]) begin
if (pixel_axis.tdata[lane*8+:8] != expected_rgb[output_index]) begin
$fatal(1, "decoded byte mismatch at %0d: got=%h expected=%h", output_index,
pixel_axis.tdata[lane*8+:8], expected_rgb[output_index]);
end
output_index = output_index + 1;
end
end
end
end
initial begin
if (!$value$plusargs(
"input_hex=%s", input_path
) || !$value$plusargs(
"input_size=%d", input_size
) || !$value$plusargs(
"expected_rgb_hex=%s", expected_path
) || !$value$plusargs(
"expected_rgb_size=%d", expected_size
)) begin
$fatal(1, "decode core test requires JPEG and RGB plusargs");
end
$readmemh(input_path, input_bytes);
$readmemh(expected_path, expected_rgb);
start = 1'b0;
bitstream_axis.tdata = '0;
bitstream_axis.tkeep = '0;
bitstream_axis.tstrb = '0;
bitstream_axis.tlast = 1'b0;
bitstream_axis.tid = '0;
bitstream_axis.tdest = '0;
bitstream_axis.tuser = '0;
bitstream_axis.tvalid = 1'b0;
pixel_axis.tready = 1'b1;
output_index = 0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
@(negedge clk);
start = 1'b1;
@(negedge clk);
start = 1'b0;
for (int offset = 0; offset < input_size; offset += 8) begin
send_input_beat(offset);
end
for (int cycle = 0; cycle < 200000 && !done && !error_flag; cycle++) begin
@(negedge clk);
end
if (error_flag || !done || width != 16'd16 || height != 16'd16 || sampling != 2'd3 ||
output_index != expected_size) begin
$fatal(1,
"decode completion mismatch: done=%0d err=%0d code=%0d size=%0dx%0d sampling=%0d bytes=%0d expected=%0d",
done, error_flag, error_code, width, height, sampling, output_index, expected_size);
end
$display("JPEG decode core tests passed");
$finish;
end
endmodule
+198
View File
@@ -0,0 +1,198 @@
`timescale 1ns / 1ps
`include "axi4_define.svh"
module jpeg_dma_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic read_start;
logic write_start;
logic read_busy;
logic write_busy;
logic read_done;
logic write_done;
logic error_flag;
logic [31:0] read_bytes;
logic [31:0] write_bytes;
logic [63:0] memory [0:2047];
logic read_active;
logic [31:0] read_base;
logic [ 7:0] read_len;
logic [ 7:0] read_index;
logic write_active;
logic [31:0] write_base;
logic [ 7:0] write_len;
logic [ 7:0] write_index;
logic bvalid;
int read_bursts;
axi4_if #(
.DATA_WIDTH(64),
.ID_WIDTH (3)
) axi4 (
.aclk (clk),
.aresetn(rst_n)
);
axi4_stream_if #(
.DATA_WIDTH(64)
) read_axis (
.aclk (clk),
.aresetn(rst_n)
);
axi4_stream_if #(
.DATA_WIDTH(64)
) write_axis (
.aclk (clk),
.aresetn(rst_n)
);
always #5 clk = ~clk;
assign write_axis.tdata = read_axis.tdata;
assign write_axis.tkeep = read_axis.tkeep;
assign write_axis.tstrb = read_axis.tstrb;
assign write_axis.tlast = read_axis.tlast;
assign write_axis.tid = '0;
assign write_axis.tdest = '0;
assign write_axis.tuser = read_axis.tuser;
assign write_axis.tvalid = read_axis.tvalid;
assign read_axis.tready = write_axis.tready;
jpeg_dma u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.abort_i (1'b0),
.quiesce_i (1'b0),
.read_start_i (read_start),
.read_addr_i (32'h00000ff0),
.read_line_bytes_i (32'd20),
.read_stride_i (32'd32),
.read_lines_i (16'd2),
.read_busy_o (read_busy),
.read_done_o (read_done),
.read_axis (read_axis),
.write_start_i (write_start),
.write_addr_i (32'h00002000),
.write_line_bytes_i(32'd20),
.write_stride_i (32'd32),
.write_lines_i (16'd2),
.write_busy_o (write_busy),
.write_done_o (write_done),
.write_axis (write_axis),
.error_o (error_flag),
.error_read_o (),
.error_resp_o (),
.error_addr_o (),
.read_bytes_o (read_bytes),
.write_bytes_o (write_bytes),
.read_stall_o (),
.write_stall_o (),
.axi4 (axi4)
);
assign axi4.arready = !read_active;
assign axi4.rid = 3'd0;
assign axi4.rdata = memory[(read_base>>3)+read_index];
assign axi4.rresp = `AXI4_RESP_OKAY;
assign axi4.rlast = read_index == read_len;
assign axi4.ruser = '0;
assign axi4.rvalid = read_active;
assign axi4.awready = !write_active && !bvalid;
assign axi4.wready = write_active;
assign axi4.bid = 3'd0;
assign axi4.bresp = `AXI4_RESP_OKAY;
assign axi4.buser = '0;
assign axi4.bvalid = bvalid;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
read_active <= 1'b0;
read_base <= '0;
read_len <= '0;
read_index <= '0;
write_active <= 1'b0;
write_base <= '0;
write_len <= '0;
write_index <= '0;
bvalid <= 1'b0;
read_bursts <= 0;
end else begin
if (axi4.arvalid && axi4.arready) begin
read_active <= 1'b1;
read_base <= axi4.araddr;
read_len <= axi4.arlen;
read_index <= 8'd0;
read_bursts <= read_bursts + 1;
end else if (axi4.rvalid && axi4.rready) begin
if (axi4.rlast) begin
read_active <= 1'b0;
end else begin
read_index <= read_index + 1'b1;
end
end
if (axi4.awvalid && axi4.awready) begin
write_active <= 1'b1;
write_base <= axi4.awaddr;
write_len <= axi4.awlen;
write_index <= 8'd0;
end
if (axi4.wvalid && axi4.wready) begin
for (int byte_index = 0; byte_index < 8; byte_index++) begin
if (axi4.wstrb[byte_index]) begin
memory[(write_base>>3)+write_index][byte_index*8+:8] <= axi4.wdata[byte_index*8+:8];
end
end
if (axi4.wlast) begin
write_active <= 1'b0;
bvalid <= 1'b1;
end else begin
write_index <= write_index + 1'b1;
end
end
if (axi4.bvalid && axi4.bready) begin
bvalid <= 1'b0;
end
end
end
initial begin
for (int index = 0; index < 2048; index++) begin
memory[index] = 64'hdeadbeefdeadbeef;
end
memory[32'h0ff0>>3] = 64'h0706050403020100;
memory[32'h0ff8>>3] = 64'h0f0e0d0c0b0a0908;
memory[32'h1000>>3] = 64'h1716151413121110;
memory[32'h1010>>3] = 64'h2726252423222120;
memory[32'h1018>>3] = 64'h2f2e2d2c2b2a2928;
memory[32'h1020>>3] = 64'h3736353433323130;
read_start = 1'b0;
write_start = 1'b0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
@(negedge clk);
read_start = 1'b1;
write_start = 1'b1;
@(negedge clk);
read_start = 1'b0;
write_start = 1'b0;
while (read_busy || write_busy) @(negedge clk);
if (error_flag || read_bytes != 32'd40 || write_bytes != 32'd40) begin
$fatal(1, "DMA status mismatch: err=%0d read=%0d write=%0d", error_flag, read_bytes,
write_bytes);
end
if (read_bursts < 3) $fatal(1, "4 KiB or line boundary was not split");
if (memory[32'h2000>>3] != 64'h0706050403020100 ||
memory[32'h2008>>3] != 64'h0f0e0d0c0b0a0908 ||
memory[32'h2010>>3][31:0] != 32'h13121110 ||
memory[32'h2020>>3] != 64'h2726252423222120 ||
memory[32'h2028>>3] != 64'h2f2e2d2c2b2a2928 ||
memory[32'h2030>>3][31:0] != 32'h33323130) begin
$fatal(1, "2D DMA payload mismatch");
end
if (memory[32'h2010>>3][63:32] != 32'hdeadbeef ||
memory[32'h2030>>3][63:32] != 32'hdeadbeef) begin
$fatal(1, "tail strobe corrupted memory");
end
$display("JPEG DMA tests passed");
$finish;
end
endmodule
+219
View File
@@ -0,0 +1,219 @@
`timescale 1ns / 1ps
module jpeg_encode_core_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic start;
logic [ 511:0] luma_quant;
logic [1599:0] luma_reciprocal;
logic [ 191:0] luma_dc_code;
logic [ 59:0] luma_dc_length;
logic [4095:0] luma_ac_code;
logic [1279:0] luma_ac_length;
logic [ 511:0] chroma_quant;
logic [1599:0] chroma_reciprocal;
logic [ 191:0] chroma_dc_code;
logic [ 59:0] chroma_dc_length;
logic [4095:0] chroma_ac_code;
logic [1279:0] chroma_ac_length;
logic busy;
logic done;
logic error_flag;
logic [ 7:0] expected [0:2047];
logic [ 7:0] luma_quant_values [ 0:63];
logic [ 7:0] chroma_quant_values [ 0:63];
logic [ 31:0] luma_dc_entries [ 0:11];
logic [ 31:0] chroma_dc_entries [ 0:11];
logic [ 31:0] luma_ac_entries [ 0:255];
logic [ 31:0] chroma_ac_entries [ 0:255];
integer expected_size;
integer output_index;
integer input_beats;
integer expected_input_beats;
integer test_width;
integer test_height;
integer cycle_count;
integer timeout_cycles;
string expected_path;
string luma_quant_path;
string chroma_quant_path;
string luma_dc_path;
string chroma_dc_path;
string luma_ac_path;
string chroma_ac_path;
axi4_stream_if #(
.DATA_WIDTH(64)
) pixel_axis (
.aclk (clk),
.aresetn(rst_n)
);
axi4_stream_if #(
.DATA_WIDTH(64)
) bitstream_axis (
.aclk (clk),
.aresetn(rst_n)
);
always #5 clk = ~clk;
jpeg_encode_core u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.width_i (test_width[15:0]),
.height_i (test_height[15:0]),
.sampling_i (2'd3),
.input_format_i (3'd2),
.restart_interval_i (16'd0),
.luma_quant_i (luma_quant),
.luma_reciprocal_i (luma_reciprocal),
.luma_dc_code_i (luma_dc_code),
.luma_dc_length_i (luma_dc_length),
.luma_ac_code_i (luma_ac_code),
.luma_ac_length_i (luma_ac_length),
.chroma_quant_i (chroma_quant),
.chroma_reciprocal_i(chroma_reciprocal),
.chroma_dc_code_i (chroma_dc_code),
.chroma_dc_length_i (chroma_dc_length),
.chroma_ac_code_i (chroma_ac_code),
.chroma_ac_length_i (chroma_ac_length),
.pixel_axis (pixel_axis),
.bitstream_axis (bitstream_axis),
.start_ready_o (),
.busy_o (busy),
.done_o (done),
.error_o (error_flag)
);
always @(posedge clk) begin
if (rst_n && busy) begin
cycle_count <= cycle_count + 1;
end
if (rst_n && pixel_axis.tvalid && pixel_axis.tready) begin
input_beats <= input_beats + 1;
end
if (rst_n && bitstream_axis.tvalid && bitstream_axis.tready) begin
for (int lane = 0; lane < 8; lane++) begin
if (bitstream_axis.tkeep[lane]) begin
if (bitstream_axis.tdata[lane*8+:8] != expected[output_index]) begin
$fatal(1, "encoded byte mismatch at %0d: got=%h expected=%h", output_index,
bitstream_axis.tdata[lane*8+:8], expected[output_index]);
end
output_index = output_index + 1;
end
end
if (bitstream_axis.tlast != (output_index == expected_size)) begin
$fatal(1, "encoded TLAST mismatch");
end
end
end
initial begin
if (!$value$plusargs(
"expected_hex=%s", expected_path
) || !$value$plusargs(
"expected_size=%d", expected_size
) || !$value$plusargs(
"luma_quant_hex=%s", luma_quant_path
) || !$value$plusargs(
"chroma_quant_hex=%s", chroma_quant_path
) || !$value$plusargs(
"luma_dc_hex=%s", luma_dc_path
) || !$value$plusargs(
"chroma_dc_hex=%s", chroma_dc_path
) || !$value$plusargs(
"luma_ac_hex=%s", luma_ac_path
) || !$value$plusargs(
"chroma_ac_hex=%s", chroma_ac_path
)) begin
$fatal(1, "encode core test requires JPEG and table plusargs");
end
test_width = 16;
test_height = 16;
expected_input_beats = 96;
void'($value$plusargs("test_width=%d", test_width));
void'($value$plusargs("test_height=%d", test_height));
void'($value$plusargs("input_beats=%d", expected_input_beats));
$readmemh(expected_path, expected);
$readmemh(luma_quant_path, luma_quant_values);
$readmemh(chroma_quant_path, chroma_quant_values);
$readmemh(luma_dc_path, luma_dc_entries);
$readmemh(chroma_dc_path, chroma_dc_entries);
$readmemh(luma_ac_path, luma_ac_entries);
$readmemh(chroma_ac_path, chroma_ac_entries);
luma_quant = '0;
luma_reciprocal = '0;
chroma_quant = '0;
chroma_reciprocal = '0;
luma_dc_code = '0;
luma_dc_length = '0;
chroma_dc_code = '0;
chroma_dc_length = '0;
luma_ac_code = '0;
luma_ac_length = '0;
chroma_ac_code = '0;
chroma_ac_length = '0;
for (int index = 0; index < 64; index++) begin
luma_quant[index*8+:8] = luma_quant_values[index];
chroma_quant[index*8+:8] = chroma_quant_values[index];
luma_reciprocal[index*25+:25] =
(25'd16777216 + luma_quant_values[index] - 1'b1) / luma_quant_values[index];
chroma_reciprocal[index*25+:25] =
(25'd16777216 + chroma_quant_values[index] - 1'b1) / chroma_quant_values[index];
end
for (int index = 0; index < 12; index++) begin
luma_dc_code[index*16+:16] = luma_dc_entries[index][15:0];
luma_dc_length[index*5+:5] = luma_dc_entries[index][20:16];
chroma_dc_code[index*16+:16] = chroma_dc_entries[index][15:0];
chroma_dc_length[index*5+:5] = chroma_dc_entries[index][20:16];
end
for (int index = 0; index < 256; index++) begin
luma_ac_code[index*16+:16] = luma_ac_entries[index][15:0];
luma_ac_length[index*5+:5] = luma_ac_entries[index][20:16];
chroma_ac_code[index*16+:16] = chroma_ac_entries[index][15:0];
chroma_ac_length[index*5+:5] = chroma_ac_entries[index][20:16];
end
start = 1'b0;
pixel_axis.tdata = {8{8'd128}};
pixel_axis.tkeep = 8'hff;
pixel_axis.tstrb = 8'hff;
pixel_axis.tlast = 1'b0;
pixel_axis.tid = '0;
pixel_axis.tdest = '0;
pixel_axis.tuser = '0;
pixel_axis.tvalid = 1'b0;
bitstream_axis.tready = 1'b1;
output_index = 0;
input_beats = 0;
cycle_count = 0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
@(negedge clk);
start = 1'b1;
@(negedge clk);
start = 1'b0;
pixel_axis.tvalid = 1'b1;
while (input_beats < expected_input_beats) begin
@(negedge clk);
pixel_axis.tlast = input_beats + 1 == expected_input_beats;
end
pixel_axis.tvalid = 1'b0;
pixel_axis.tlast = 1'b0;
timeout_cycles = 3000;
while (!done && (timeout_cycles > 0)) begin
@(negedge clk);
timeout_cycles--;
end
if (timeout_cycles == 0) begin
$fatal(1, "encode timeout: core=%0d builder=%0d block=%0d", u_dut.s_state_q,
u_dut.u_mcu_builder.s_state_q, u_dut.u_block_encoder.s_state_q);
end
if (error_flag || output_index != expected_size) begin
$fatal(1, "encode completion mismatch: err=%0d bytes=%0d expected=%0d", error_flag,
output_index, expected_size);
end
$display("JPEG encode core tests passed cycles=%0d", cycle_count);
$finish;
end
endmodule
+95
View File
@@ -0,0 +1,95 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module jpeg_entropy_decoder_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic start;
logic signed [ 23:0] previous_dc;
logic [ 191:0] dc_code;
logic [ 59:0] dc_length;
logic [4095:0] ac_code;
logic [1279:0] ac_length;
logic [ 31:0] bit_window;
logic [ 5:0] bit_count;
logic [ 5:0] bit_consume;
logic bit_consume_valid;
logic start_ready;
logic [1535:0] block_out;
logic signed [ 23:0] dc_out;
logic result_valid;
logic result_ready;
logic error_flag;
always #5 clk = ~clk;
jpeg_entropy_decoder u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.previous_dc_i (previous_dc),
.dc_code_i (dc_code),
.dc_length_i (dc_length),
.ac_code_i (ac_code),
.ac_length_i (ac_length),
.bit_window_i (bit_window),
.bit_count_i (bit_count),
.bit_consume_o (bit_consume),
.bit_consume_valid_o(bit_consume_valid),
.start_ready_o (start_ready),
.block_o (block_out),
.dc_o (dc_out),
.result_valid_o (result_valid),
.result_ready_i (result_ready),
.error_o (error_flag)
);
always @(posedge clk) begin
if (rst_n && bit_consume_valid) begin
bit_window <= bit_window << bit_consume;
bit_count <= bit_count - bit_consume;
end
end
initial begin
start = 1'b0;
previous_dc = 24'sd2;
dc_code = '0;
dc_length = '0;
ac_code = '0;
ac_length = '0;
bit_window = {23'b10111_1110_1110_1110_101011, 9'd0};
bit_count = 6'd23;
result_ready = 1'b0;
dc_code[2*16+:16] = 16'b101;
dc_length[2*5+:5] = 5'd3;
ac_code[8'hf0*16+:16] = 16'b1110;
ac_length[8'hf0*5+:5] = 5'd4;
ac_code[8'he1*16+:16] = 16'b10101;
ac_length[8'he1*5+:5] = 5'd5;
repeat (3) @(posedge clk);
rst_n = 1'b1;
@(negedge clk);
start = 1'b1;
@(negedge clk);
start = 1'b0;
while (!result_valid) @(negedge clk);
if (error_flag || dc_out != 24'sd5 || $signed(
block_out[0+:24]
) != 24'sd5 || $signed(
block_out[63*24+:24]
) != 24'sd1 || bit_count != 6'd0) begin
$fatal(1, "entropy decode mismatch: err=%0d dc=%0d last=%0d bits=%0d", error_flag, dc_out,
$signed(block_out[63*24+:24]), bit_count);
end
for (int index = 1; index < 63; index++) begin
if ($signed(block_out[index*24+:24]) != 24'sd0) begin
$fatal(1, "unexpected coefficient at %0d", index);
end
end
result_ready = 1'b1;
@(negedge clk);
$display("JPEG entropy decoder tests passed");
$finish;
end
endmodule
+115
View File
@@ -0,0 +1,115 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module jpeg_entropy_encoder_tb;
logic clk;
logic rst_n;
logic start;
logic [1535:0] block_in;
logic signed [ 23:0] previous_dc;
logic [ 191:0] dc_code;
logic [ 59:0] dc_length;
logic [4095:0] ac_code;
logic [1279:0] ac_length;
logic start_ready;
logic signed [ 23:0] dc_out;
logic [ 127:0] token_bits;
logic [ 23:0] token_length;
logic [ 2:0] token_count;
logic token_valid;
logic token_last;
logic token_ready;
logic error_flag;
logic [ 31:0] captured_bits [0:4];
logic [ 5:0] captured_length[0:4];
int capture_count;
jpeg_entropy_encoder u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.block_i (block_in),
.previous_dc_i (previous_dc),
.dc_code_i (dc_code),
.dc_length_i (dc_length),
.ac_code_i (ac_code),
.ac_length_i (ac_length),
.start_ready_o (start_ready),
.dc_o (dc_out),
.token_bits_o (token_bits),
.token_length_o(token_length),
.token_count_o (token_count),
.token_valid_o (token_valid),
.token_last_o (token_last),
.token_ready_i (token_ready),
.error_o (error_flag)
);
always #5 clk = ~clk;
always @(posedge clk) begin
if (rst_n && token_valid && token_ready) begin
for (int index = 0; index < token_count; index++) begin
captured_bits[capture_count] = token_bits[index*32+:32];
captured_length[capture_count] = token_length[index*6+:6];
capture_count = capture_count + 1;
end
end
end
initial begin
clk = 1'b0;
rst_n = 1'b0;
start = 1'b0;
block_in = '0;
previous_dc = 24'sd2;
dc_code = '0;
dc_length = '0;
ac_code = '0;
ac_length = '0;
token_ready = 1'b0;
capture_count = 0;
block_in[0+:24] = 24'sd5;
block_in[63*24+:24] = 24'sd1;
dc_code[2*16+:16] = 16'b101;
dc_length[2*5+:5] = 5'd3;
ac_code[8'hf0*16+:16] = 16'b1110;
ac_length[8'hf0*5+:5] = 5'd4;
ac_code[8'he1*16+:16] = 16'b10101;
ac_length[8'he1*5+:5] = 5'd5;
repeat (3) @(negedge clk);
rst_n = 1'b1;
@(negedge clk);
start = 1'b1;
@(negedge clk);
start = 1'b0;
while (!token_valid) @(negedge clk);
repeat (2) begin
@(negedge clk);
if (token_bits[0+:32] != 32'd23 || token_length[0+:6] != 6'd5) begin
$fatal(1, "token changed under backpressure");
end
end
token_ready = 1'b1;
while (!start_ready) @(negedge clk);
@(negedge clk);
if (error_flag) $fatal(1, "unexpected entropy error");
if (dc_out != 24'sd5 || capture_count != 5) begin
$fatal(1, "entropy completion mismatch: dc=%0d tokens=%0d", dc_out, capture_count);
end
if (captured_bits[0] != 32'd23 || captured_length[0] != 6'd5) begin
$fatal(1, "DC token mismatch");
end
for (int index = 1; index < 4; index++) begin
if (captured_bits[index] != 32'd14 || captured_length[index] != 6'd4) begin
$fatal(1, "ZRL token mismatch at %0d", index);
end
end
if (captured_bits[4] != 32'd43 || captured_length[4] != 6'd6) begin
$fatal(1, "final AC token mismatch");
end
$display("JPEG entropy encoder tests passed");
$finish;
end
endmodule
+96
View File
@@ -0,0 +1,96 @@
`timescale 1ns / 1ps
module jpeg_header_writer_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic start;
logic [511:0] luma_quant;
logic [511:0] chroma_quant;
logic [ 7:0] output_byte;
logic output_valid;
logic output_ready;
logic done;
logic error_flag;
logic [ 7:0] expected [0:1023];
logic [ 7:0] luma_values [ 0:63];
logic [ 7:0] chroma_values [ 0:63];
integer expected_size;
integer output_index;
string header_path;
string luma_path;
string chroma_path;
always #5 clk = ~clk;
jpeg_header_writer u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.width_i (16'd17),
.height_i (16'd15),
.sampling_i (2'd3),
.restart_interval_i(16'd1),
.luma_quant_i (luma_quant),
.chroma_quant_i (chroma_quant),
.start_ready_o (),
.byte_o (output_byte),
.byte_valid_o (output_valid),
.byte_ready_i (output_ready),
.done_o (done),
.error_o (error_flag)
);
always @(posedge clk) begin
if (rst_n && output_valid && output_ready) begin
if (output_byte != expected[output_index]) begin
$fatal(1, "header byte mismatch at %0d: got=%h expected=%h", output_index, output_byte,
expected[output_index]);
end
output_index <= output_index + 1;
end
end
initial begin
if (!$value$plusargs(
"header_hex=%s", header_path
) || !$value$plusargs(
"luma_hex=%s", luma_path
) || !$value$plusargs(
"chroma_hex=%s", chroma_path
) || !$value$plusargs(
"header_size=%d", expected_size
)) begin
$fatal(1, "header writer test requires header and quantization plusargs");
end
$readmemh(header_path, expected);
$readmemh(luma_path, luma_values);
$readmemh(chroma_path, chroma_values);
luma_quant = '0;
chroma_quant = '0;
for (int index = 0; index < 64; index++) begin
luma_quant[index*8+:8] = luma_values[index];
chroma_quant[index*8+:8] = chroma_values[index];
end
start = 1'b0;
output_ready = 1'b0;
output_index = 0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
@(negedge clk);
start = 1'b1;
@(negedge clk);
start = 1'b0;
repeat (2) @(negedge clk);
if (!output_valid || output_byte != 8'hff) begin
$fatal(1, "header output changed under backpressure");
end
output_ready = 1'b1;
while (!done) @(negedge clk);
if (error_flag || output_index != expected_size) begin
$fatal(1, "header completion mismatch: err=%0d bytes=%0d expected=%0d", error_flag,
output_index, expected_size);
end
$display("JPEG header writer tests passed");
$finish;
end
endmodule
+145
View File
@@ -0,0 +1,145 @@
`timescale 1ns / 1ps
module jpeg_marker_parser_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic start;
logic [ 7:0] byte_data;
logic byte_valid;
logic byte_ready;
logic [ 3:0] table_kind;
logic [ 7:0] table_index;
logic [31:0] table_data;
logic table_write;
logic table_commit;
logic header_valid;
logic header_ready;
logic entropy;
logic [15:0] width;
logic [15:0] height;
logic [ 1:0] sampling;
logic [ 1:0] component_count;
logic [15:0] restart_interval;
logic error_flag;
logic [ 4:0] error_code;
logic [ 7:0] input_bytes [0:4095];
integer input_size;
integer input_index;
integer table_writes;
integer table_commits;
logic quant_zero_seen;
logic dc_zero_seen;
logic [31:0] actual_tables [0:3071];
logic [31:0] expected_tables [0:3071];
string hex_path;
string tables_path;
always #5 clk = ~clk;
assign byte_data = input_bytes[input_index];
assign byte_valid = input_index < input_size;
jpeg_marker_parser u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.table_context_i (2'd2),
.byte_i (byte_data),
.byte_valid_i (byte_valid),
.byte_ready_o (byte_ready),
.table_context_o (),
.table_kind_o (table_kind),
.table_index_o (table_index),
.table_data_o (table_data),
.table_write_o (table_write),
.table_commit_o (table_commit),
.header_valid_o (header_valid),
.header_ready_i (header_ready),
.entropy_o (entropy),
.width_o (width),
.height_o (height),
.sampling_o (sampling),
.component_count_o (component_count),
.component_id_o (),
.component_factor_o(),
.quant_table_o (),
.dc_table_o (),
.ac_table_o (),
.restart_interval_o(restart_interval),
.marker_count_o (),
.error_o (error_flag),
.error_code_o (error_code)
);
always @(posedge clk) begin
if (rst_n && byte_valid && byte_ready) begin
input_index <= input_index + 1;
end
if (rst_n && table_write) begin
actual_tables[(table_kind*256)+table_index] <= table_data;
table_writes <= table_writes + 1;
if ((table_kind == 4'd0) && (table_index == 8'd0) && (table_data[7:0] == 8'd8)) begin
quant_zero_seen <= 1'b1;
end
if ((table_kind == 4'd4) && (table_index == 8'd0) && (table_data == 32'h00020000)) begin
dc_zero_seen <= 1'b1;
end
end
if (rst_n && table_commit) begin
table_commits <= table_commits + 1;
end
end
initial begin
if (!$value$plusargs(
"jpeg_hex=%s", hex_path
) || !$value$plusargs(
"tables_hex=%s", tables_path
) || !$value$plusargs(
"jpeg_size=%d", input_size
)) begin
$fatal(1, "JPEG parser test requires jpeg_hex and jpeg_size plusargs");
end
$readmemh(hex_path, input_bytes);
$readmemh(tables_path, expected_tables);
input_index = 0;
table_writes = 0;
table_commits = 0;
quant_zero_seen = 1'b0;
dc_zero_seen = 1'b0;
for (int index = 0; index < 3072; index++) begin
actual_tables[index] = 32'd0;
end
start = 1'b0;
header_ready = 1'b0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
@(negedge clk);
start = 1'b1;
@(negedge clk);
start = 1'b0;
while (!header_valid && !error_flag) @(negedge clk);
if (error_flag)
$fatal(1, "header parser failed with code %0d at byte %0d", error_code, input_index);
if (width != 16'd17 || height != 16'd15 || sampling != 2'd3 ||
component_count != 2'd3 || restart_interval != 16'd1 || table_writes != 476 ||
table_commits != 1 || !quant_zero_seen || !dc_zero_seen) begin
$fatal(
1,
"header result mismatch: %0dx%0d sampling=%0d components=%0d restart=%0d writes=%0d commits=%0d q=%0d dc=%0d",
width, height, sampling, component_count, restart_interval, table_writes, table_commits,
quant_zero_seen, dc_zero_seen);
end
for (int index = 0; index < 3072; index++) begin
if (actual_tables[index] != expected_tables[index]) begin
$fatal(1, "expanded table mismatch at %0d: got=%h expected=%h", index,
actual_tables[index], expected_tables[index]);
end
end
header_ready = 1'b1;
@(negedge clk);
if (!entropy) $fatal(1, "parser did not enter entropy mode");
$display("JPEG marker parser tests passed");
$finish;
end
endmodule
+86
View File
@@ -0,0 +1,86 @@
`timescale 1ns / 1ps
module jpeg_mcu_builder_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic start;
logic [511:0] block_data;
logic block_valid;
logic block_last;
logic done;
logic error_flag;
int input_beats;
int output_blocks;
axi4_stream_if #(
.DATA_WIDTH(64)
) pixel_axis (
.aclk (clk),
.aresetn(rst_n)
);
always #5 clk = ~clk;
jpeg_mcu_builder u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.sampling_i (2'd3),
.format_i (3'd2),
.valid_width_i (5'd16),
.valid_height_i(5'd16),
.pixel_axis (pixel_axis),
.start_ready_o (),
.block_o (block_data),
.block_valid_o (block_valid),
.block_ready_i (1'b1),
.block_last_o (block_last),
.done_o (done),
.error_o (error_flag)
);
always @(posedge clk) begin
if (rst_n && pixel_axis.tvalid && pixel_axis.tready) begin
input_beats <= input_beats + 1;
end
if (rst_n && block_valid) begin
if (block_data != {64{8'd128}} || (block_last != (output_blocks == 5))) begin
$fatal(1, "generated block mismatch at %0d", output_blocks);
end
output_blocks <= output_blocks + 1;
end
end
initial begin
start = 1'b0;
pixel_axis.tdata = {8{8'd128}};
pixel_axis.tkeep = 8'hff;
pixel_axis.tstrb = 8'hff;
pixel_axis.tlast = 1'b0;
pixel_axis.tid = '0;
pixel_axis.tdest = '0;
pixel_axis.tuser = '0;
pixel_axis.tvalid = 1'b0;
input_beats = 0;
output_blocks = 0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
@(negedge clk);
start = 1'b1;
@(negedge clk);
start = 1'b0;
pixel_axis.tvalid = 1'b1;
while (input_beats < 96) begin
@(negedge clk);
pixel_axis.tlast = input_beats == 95;
end
pixel_axis.tvalid = 1'b0;
pixel_axis.tlast = 1'b0;
while (!done) @(negedge clk);
if (error_flag || output_blocks != 6) begin
$fatal(1, "MCU builder completion mismatch: err=%0d blocks=%0d", error_flag, output_blocks);
end
$display("JPEG MCU builder tests passed");
$finish;
end
endmodule
+90
View File
@@ -0,0 +1,90 @@
`timescale 1ns / 1ps
module jpeg_mcu_reconstructor_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic start;
logic [511:0] block_data;
logic block_valid;
logic block_ready;
logic done;
logic error_flag;
int block_index;
int beat_count;
int line_count;
axi4_stream_if #(
.DATA_WIDTH(64)
) pixel_axis (
.aclk (clk),
.aresetn(rst_n)
);
always #5 clk = ~clk;
jpeg_mcu_reconstructor u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.sampling_i (2'd3),
.format_i (3'd2),
.valid_width_i (5'd16),
.valid_height_i(5'd16),
.block_i (block_data),
.block_valid_i (block_valid),
.block_ready_o (block_ready),
.start_ready_o (),
.pixel_axis (pixel_axis),
.done_o (done),
.error_o (error_flag)
);
always @(posedge clk) begin
if (rst_n && block_valid && block_ready) begin
block_index <= block_index + 1;
end
if (rst_n && pixel_axis.tvalid && pixel_axis.tready) begin
if (pixel_axis.tkeep != 8'h3f || pixel_axis.tdata[47:0] != 48'h808080808080) begin
$fatal(1, "RGB888 raster output mismatch");
end
if (pixel_axis.tuser != (beat_count == 0)) begin
$fatal(1, "SOF marker mismatch at beat %0d", beat_count);
end
if (pixel_axis.tlast) begin
line_count <= line_count + 1;
end
beat_count <= beat_count + 1;
end
end
initial begin
start = 1'b0;
block_data = {64{8'd128}};
block_valid = 1'b0;
pixel_axis.tready = 1'b0;
block_index = 0;
beat_count = 0;
line_count = 0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
@(negedge clk);
start = 1'b1;
@(negedge clk);
start = 1'b0;
block_valid = 1'b1;
while (block_index < 6) @(negedge clk);
block_valid = 1'b0;
repeat (2) @(negedge clk);
if (!pixel_axis.tvalid || pixel_axis.tdata[47:0] != 48'h808080808080) begin
$fatal(1, "pixel output not stable during backpressure");
end
pixel_axis.tready = 1'b1;
while (!done) @(negedge clk);
if (error_flag || beat_count != 128 || line_count != 16) begin
$fatal(1, "MCU completion mismatch: err=%0d beats=%0d lines=%0d", error_flag, beat_count,
line_count);
end
$display("JPEG MCU reconstructor tests passed");
$finish;
end
endmodule
+190
View File
@@ -0,0 +1,190 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module jpeg_quantizer_tb;
logic clk;
logic rst_n;
logic start;
logic dequantize;
logic [1535:0] block_in;
logic [ 511:0] quant;
logic [1599:0] reciprocal;
logic start_ready;
logic [1535:0] block_out;
logic result_valid;
logic result_ready;
logic table_err;
logic overflow;
jpeg_quantizer u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.dequantize_i (dequantize),
.block_i (block_in),
.quant_i (quant),
.reciprocal_i (reciprocal),
.start_ready_o (start_ready),
.block_o (block_out),
.result_valid_o(result_valid),
.result_ready_i(result_ready),
.table_err_o (table_err),
.overflow_o (overflow)
);
always #5 clk = ~clk;
function automatic logic [7:0] luma_quant(input int index_i);
logic [7:0] values[0:63];
begin
values = '{
16,
11,
10,
16,
24,
40,
51,
61,
12,
12,
14,
19,
26,
58,
60,
55,
14,
13,
16,
24,
40,
57,
69,
56,
14,
17,
22,
29,
51,
87,
80,
62,
18,
22,
37,
56,
68,
109,
103,
77,
24,
35,
55,
64,
81,
104,
113,
92,
49,
64,
78,
87,
103,
121,
120,
101,
72,
92,
95,
98,
112,
100,
103,
99
};
return values[index_i];
end
endfunction
function automatic logic signed [23:0] coefficient(input int index_i);
begin
case (index_i)
0: coefficient = -24'sd4;
1: coefficient = -24'sd17;
3: coefficient = -24'sd3;
8: coefficient = -24'sd146;
24: coefficient = -24'sd15;
40: coefficient = -24'sd5;
56: coefficient = -24'sd1;
default: coefficient = 24'sd0;
endcase
end
endfunction
function automatic logic signed [23:0] quantized(input int index_i);
begin
case (index_i)
1: quantized = -24'sd2;
8: quantized = -24'sd12;
24: quantized = -24'sd1;
default: quantized = 24'sd0;
endcase
end
endfunction
task automatic run_operation(input logic dequantize_i);
begin
@(negedge clk);
dequantize = dequantize_i;
start = 1'b1;
@(negedge clk);
start = 1'b0;
while (!result_valid) @(negedge clk);
end
endtask
initial begin
clk = 1'b0;
rst_n = 1'b0;
start = 1'b0;
dequantize = 1'b0;
block_in = '0;
quant = '0;
reciprocal = '0;
result_ready = 1'b0;
for (int index = 0; index < 64; index++) begin
block_in[index*24+:24] = coefficient(index);
quant[index*8+:8] = luma_quant(index);
reciprocal[index*25+:25] = (25'd16777216 + luma_quant(index) - 1'b1) / luma_quant(index);
end
repeat (3) @(negedge clk);
rst_n = 1'b1;
run_operation(1'b0);
if (table_err || overflow) $fatal(1, "unexpected quantizer error");
for (int index = 0; index < 64; index++) begin
if ($signed(block_out[index*24+:24]) != quantized(index)) begin
$fatal(1, "quantized mismatch at %0d: %0d", index, $signed(block_out[index*24+:24]));
end
end
block_in = block_out;
result_ready = 1'b1;
@(negedge clk);
result_ready = 1'b0;
run_operation(1'b1);
if ($signed(
block_out[1*24+:24]
) != -24'sd22 || $signed(
block_out[8*24+:24]
) != -24'sd144 || $signed(
block_out[24*24+:24]
) != -24'sd14) begin
$fatal(1, "dequantized output mismatch");
end
result_ready = 1'b1;
@(negedge clk);
$display("JPEG quantizer tests passed");
$finish;
end
endmodule
+190
View File
@@ -0,0 +1,190 @@
`timescale 1ns / 1ps
module jpeg_reg_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic busy;
logic job_done;
logic error_event;
logic start_pulse;
logic table_write;
logic [7:0] table_index;
logic irq;
apb4_if apb4 (
.pclk (clk),
.presetn(rst_n)
);
always #5 clk = ~clk;
jpeg_reg u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.apb4 (apb4),
.busy_i (busy),
.ring_active_i (1'b0),
.encode_i (1'b1),
.quiesce_i (1'b0),
.job_done_i (job_done),
.ring_event_i (1'b0),
.header_ready_i (1'b0),
.abort_done_i (1'b0),
.error_event_i (error_event),
.error_code_i (5'd9),
.error_stage_i (4'd7),
.error_axi_resp_i (2'd2),
.error_addr_i (32'h38001234),
.error_detail_i (32'h55aa55aa),
.cycles_i (64'h123456789abcdef0),
.pixels_i (32'd99),
.input_bytes_i (32'd100),
.output_bytes_i (32'd50),
.read_stall_i (32'd4),
.write_stall_i (32'd5),
.result_size_i (32'd50),
.result_image_size_i(32'h00100020),
.result_format_i (32'd3),
.result_markers_i (32'd7),
.ring_head_i (32'd2),
.ring_status_i (32'd1),
.table_data_i (32'h11223344),
.table_status_i (32'd1),
.start_o (start_pulse),
.abort_o (),
.soft_reset_o (),
.ring_kick_o (),
.job_config_o (),
.image_size_o (),
.input_format_o (),
.output_format_o (),
.encode_config_o (),
.restart_interval_o (),
.bitstream_addr_o (),
.bitstream_size_o (),
.plane0_addr_o (),
.plane0_stride_o (),
.plane1_addr_o (),
.plane1_stride_o (),
.plane2_addr_o (),
.plane2_stride_o (),
.metadata_addr_o (),
.metadata_length_o (),
.ring_base_o (),
.ring_size_o (),
.ring_tail_o (),
.ring_control_o (),
.irq_coalesce_o (),
.table_context_o (),
.table_kind_o (),
.table_index_o (table_index),
.table_write_data_o (),
.table_write_o (table_write),
.table_commit_o (),
.table_default_o (),
.table_clear_o (),
.perf_enable_o (),
.perf_clear_o (),
.irq_o (irq)
);
task automatic apb_write(input logic [11:0] offset_i, input logic [31:0] data_i,
input logic [3:0] strobe_i, input logic expected_err_i);
begin
@(negedge clk);
apb4.paddr = {20'd0, offset_i};
apb4.pwdata = data_i;
apb4.pstrb = strobe_i;
apb4.pwrite = 1'b1;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk);
apb4.penable = 1'b1;
#1;
if (!apb4.pready || (apb4.pslverr != expected_err_i)) begin
$fatal(1, "APB write response mismatch at %h", offset_i);
end
@(negedge clk);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
task automatic apb_read(input logic [11:0] offset_i, output logic [31:0] data_o,
input logic expected_err_i);
begin
@(negedge clk);
apb4.paddr = {20'd0, offset_i};
apb4.pwrite = 1'b0;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk);
apb4.penable = 1'b1;
#1;
if (!apb4.pready || (apb4.pslverr != expected_err_i)) begin
$fatal(1, "APB read response mismatch at %h", offset_i);
end
data_o = apb4.prdata;
@(negedge clk);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
initial begin
logic [31:0] value;
apb4.paddr = '0;
apb4.pprot = '0;
apb4.psel = 1'b0;
apb4.penable = 1'b0;
apb4.pwrite = 1'b0;
apb4.pwdata = '0;
apb4.pstrb = '0;
busy = 1'b0;
job_done = 1'b0;
error_event = 1'b0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
apb_read(12'h000, value, 1'b0);
if (value != 32'h4a504547) $fatal(1, "JPEG IP ID mismatch");
apb_write(12'h084, 32'h04380780, 4'hf, 1'b0);
apb_write(12'h084, 32'h00000021, 4'h1, 1'b0);
apb_read(12'h084, value, 1'b0);
if (value != 32'h04380721) $fatal(1, "byte-strobe update mismatch");
apb_write(12'h01c, 32'h1, 4'hf, 1'b0);
@(negedge clk);
job_done = 1'b1;
@(negedge clk);
job_done = 1'b0;
apb_read(12'h018, value, 1'b0);
if (value != 32'h1 || !irq) $fatal(1, "completion IRQ mismatch");
apb_write(12'h018, 32'h1, 4'h1, 1'b0);
if (irq) $fatal(1, "IRQ W1C failed");
@(negedge clk);
error_event = 1'b1;
@(negedge clk);
error_event = 1'b0;
apb_read(12'h024, value, 1'b0);
if (value[0] != 1'b1 || value[5:1] != 5'd9 || value[9:6] != 4'd7) begin
$fatal(1, "first-error status mismatch");
end
apb_read(12'h028, value, 1'b0);
if (value != 32'h38001234) $fatal(1, "first-error address mismatch");
apb_write(12'h208, 32'd7, 4'hf, 1'b0);
apb_write(12'h20c, 32'h11223344, 4'hf, 1'b0);
if (table_index != 8'd8) $fatal(1, "table portal did not auto-increment");
apb_write(12'h200, 32'd4, 4'hf, 1'b1);
apb_read(12'h010, value, 1'b1);
busy = 1'b1;
apb_write(12'h084, 32'd1, 4'hf, 1'b1);
apb_write(12'h010, 32'h1, 4'hf, 1'b1);
busy = 1'b0;
apb_write(12'h010, 32'h1, 4'hf, 1'b0);
$display("JPEG APB register tests passed");
$finish;
end
endmodule
+120
View File
@@ -0,0 +1,120 @@
`timescale 1ns / 1ps
module jpeg_table_store_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic [ 1:0] portal_context;
logic [ 3:0] portal_kind;
logic [ 7:0] portal_index;
logic [31:0] portal_write_data;
logic portal_write;
logic portal_commit;
logic portal_default;
logic portal_clear;
logic [31:0] portal_read_data;
logic [31:0] portal_status;
logic lookup;
logic [ 1:0] lookup_context;
logic [ 3:0] lookup_kind;
logic [ 7:0] lookup_index;
logic [31:0] lookup_data;
logic lookup_valid;
logic lookup_err;
always #5 clk = ~clk;
jpeg_table_store u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.portal_context_i (portal_context),
.portal_kind_i (portal_kind),
.portal_index_i (portal_index),
.portal_write_data_i(portal_write_data),
.portal_write_i (portal_write),
.portal_commit_i (portal_commit),
.portal_default_i (portal_default),
.portal_clear_i (portal_clear),
.portal_read_data_o (portal_read_data),
.portal_status_o (portal_status),
.lookup_i (lookup),
.lookup_context_i (lookup_context),
.lookup_kind_i (lookup_kind),
.lookup_index_i (lookup_index),
.lookup_data_o (lookup_data),
.lookup_valid_o (lookup_valid),
.lookup_err_o (lookup_err)
);
task automatic portal_pulse(input logic write_i, input logic commit_i, input logic clear_i);
begin
@(negedge clk);
portal_write = write_i;
portal_commit = commit_i;
portal_clear = clear_i;
@(negedge clk);
portal_write = 1'b0;
portal_commit = 1'b0;
portal_clear = 1'b0;
end
endtask
task automatic lookup_word(input logic [1:0] context_i, input logic [3:0] kind_i,
input logic [7:0] index_i, input logic [31:0] expected_i);
begin
@(negedge clk);
lookup_context = context_i;
lookup_kind = kind_i;
lookup_index = index_i;
lookup = 1'b1;
@(negedge clk);
lookup = 1'b0;
if (!lookup_valid || lookup_err || lookup_data != expected_i) begin
$fatal(1, "lookup mismatch: valid=%0d err=%0d data=%h", lookup_valid, lookup_err,
lookup_data);
end
end
endtask
initial begin
portal_context = 2'd0;
portal_kind = 4'd0;
portal_index = 8'd0;
portal_write_data = 32'd0;
portal_write = 1'b0;
portal_commit = 1'b0;
portal_default = 1'b0;
portal_clear = 1'b0;
lookup = 1'b0;
lookup_context = 2'd0;
lookup_kind = 4'd0;
lookup_index = 8'd0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
portal_context = 2'd2;
portal_kind = 4'd0;
portal_index = 8'd7;
portal_write_data = 32'h01020304;
portal_pulse(1'b1, 1'b0, 1'b0);
portal_pulse(1'b0, 1'b1, 1'b0);
if (!portal_status[3]) $fatal(1, "context commit did not set valid");
lookup_word(2'd2, 4'd0, 8'd7, 32'h01020304);
portal_context = 2'd3;
portal_kind = 4'd11;
portal_index = 8'd255;
portal_write_data = 32'ha5a55a5a;
portal_pulse(1'b1, 1'b0, 1'b0);
portal_pulse(1'b0, 1'b1, 1'b0);
lookup_word(2'd3, 4'd11, 8'd255, 32'ha5a55a5a);
portal_kind = 4'd0;
portal_index = 8'd64;
portal_pulse(1'b1, 1'b0, 1'b0);
if (!portal_status[11]) $fatal(1, "invalid portal index was not recorded");
portal_pulse(1'b0, 1'b0, 1'b1);
if (portal_status[11] || portal_status[3]) $fatal(1, "context clear failed");
$display("JPEG table store tests passed");
$finish;
end
endmodule
+96
View File
@@ -0,0 +1,96 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module jpeg_transform_tb;
logic clk;
logic rst_n;
logic start;
logic inverse;
logic [1535:0] block_in;
logic start_ready;
logic [1535:0] block_out;
logic result_valid;
logic result_ready;
jpeg_transform u_dut (
.clk_i (clk),
.rst_n_i (rst_n),
.start_i (start),
.inverse_i (inverse),
.block_i (block_in),
.start_ready_o (start_ready),
.block_o (block_out),
.result_valid_o(result_valid),
.result_ready_i(result_ready)
);
always #5 clk = ~clk;
task automatic run_transform(input logic direction_i);
begin
@(negedge clk);
inverse = direction_i;
start = 1'b1;
@(negedge clk);
start = 1'b0;
while (!result_valid) @(negedge clk);
end
endtask
function automatic logic signed [15:0] expected_coefficient(input int index_i);
begin
case (index_i)
0: expected_coefficient = -16'sd4;
1: expected_coefficient = -16'sd17;
3: expected_coefficient = -16'sd3;
8: expected_coefficient = -16'sd146;
24: expected_coefficient = -16'sd15;
40: expected_coefficient = -16'sd5;
56: expected_coefficient = -16'sd1;
default: expected_coefficient = 16'sd0;
endcase
end
endfunction
initial begin
clk = 1'b0;
rst_n = 1'b0;
start = 1'b0;
inverse = 1'b0;
block_in = '0;
result_ready = 1'b0;
repeat (3) @(negedge clk);
rst_n = 1'b1;
for (int index = 0; index < 64; index++) begin
block_in[index*24+:24] = 24'(index - 32);
end
run_transform(1'b0);
for (int index = 0; index < 64; index++) begin
if ($signed(block_out[index*24+:24]) != expected_coefficient(index)) begin
$fatal(1, "forward transform mismatch at %0d: %0d", index, $signed(
block_out[index*24+:24]));
end
end
block_in = block_out;
result_ready = 1'b1;
@(negedge clk);
result_ready = 1'b0;
run_transform(1'b1);
for (int index = 0; index < 64; index++) begin
if (($signed(
block_out[index*24+:24]
) < (index - 33)) || ($signed(
block_out[index*24+:24]
) > (index - 31))) begin
$fatal(1, "inverse transform mismatch at %0d: %0d", index, $signed(
block_out[index*24+:24]));
end
end
result_ready = 1'b1;
@(negedge clk);
$display("JPEG transform tests passed");
$finish;
end
endmodule
+2 -1
View File
@@ -42,6 +42,7 @@ module soc_irq_topology_tb;
19: expect_irq(32'd1 << 24);
20: expect_irq(32'd1 << 25);
21: expect_irq(32'd1 << 26);
22: expect_irq(32'd1 << 30);
default: expect_irq(32'd1 << (bit_index + 7));
endcase
end
@@ -69,7 +70,7 @@ module soc_irq_topology_tb;
$fatal(1, "SDIO core IRQ bits are not low when sources are low: IRQ10=%b IRQ21=%b",
s_irq[10], s_irq[21]);
end
if ((s_irq[31:30] !== '0) || (s_irq[19:18] !== '0)) begin
if ((s_irq[31] !== 1'b0) || (s_irq[19:18] !== '0)) begin
$fatal(1, "unallocated core IRQ bits are not low: %h", s_irq);
end
+929
View File
@@ -0,0 +1,929 @@
"""Directed RTL tests for the JPEG accelerator."""
from __future__ import annotations
import re
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def _write_hex_bytes(path: Path, data: bytes) -> None:
path.write_text("".join(f"{value:02x}\n" for value in data), encoding="ascii")
def _write_hex_words(path: Path, data: list[int]) -> None:
path.write_text("".join(f"{value:08x}\n" for value in data), encoding="ascii")
def test_jpeg_forward_inverse_transform(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
simulation = tmp_path / "jpeg_transform_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
"-I",
str(ROOT / "rtl/managed/clusterip/common/rtl"),
"-s",
"jpeg_transform_tb",
"-o",
str(simulation),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_transform.sv"),
str(ROOT / "tests/rtl/jpeg_transform_tb.sv"),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG transform tests passed" in result.stdout
def test_jpeg_quantize_dequantize(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
simulation = tmp_path / "jpeg_quantizer_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
"-I",
str(ROOT / "rtl/managed/clusterip/common/rtl"),
"-s",
"jpeg_quantizer_tb",
"-o",
str(simulation),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_quantizer.sv"),
str(ROOT / "tests/rtl/jpeg_quantizer_tb.sv"),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG quantizer tests passed" in result.stdout
def test_jpeg_entropy_encoder(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
simulation = tmp_path / "jpeg_entropy_encoder_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
"-I",
str(ROOT / "rtl/managed/clusterip/common/rtl"),
"-s",
"jpeg_entropy_encoder_tb",
"-o",
str(simulation),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_entropy_encoder.sv"),
str(ROOT / "tests/rtl/jpeg_entropy_encoder_tb.sv"),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG entropy encoder tests passed" in result.stdout
def test_jpeg_bit_packer_and_stuffing(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
simulation = tmp_path / "jpeg_bit_packer_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
"-I",
str(ROOT / "rtl/managed/clusterip/common/rtl"),
"-s",
"jpeg_bit_packer_tb",
"-o",
str(simulation),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_bit_packer.sv"),
str(ROOT / "tests/rtl/jpeg_bit_packer_tb.sv"),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG bit packer tests passed" in result.stdout
def test_jpeg_apb_register_path(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
source_list = tmp_path / "jpeg_reg.fl"
source_list.write_text(
"\n".join(
[
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
f"+incdir+{ROOT / 'rtl/ip/multimedia'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/apb4_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_reg.sv"),
str(ROOT / "tests/rtl/jpeg_reg_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "jpeg_reg_tb.v"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "jpeg_reg_tb"
subprocess.run(
[iverilog, "-g2012", "-s", "jpeg_reg_tb", "-o", str(simulation), str(converted)],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG APB register tests passed" in result.stdout
def test_jpeg_2d_axi_dma(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
source_list = tmp_path / "jpeg_dma.fl"
source_list.write_text(
"\n".join(
[
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl/interface'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_stream_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/peripheral/dma_axi4_master.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_dma.sv"),
str(ROOT / "tests/rtl/jpeg_dma_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "jpeg_dma_tb.v"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "jpeg_dma_tb"
subprocess.run(
[iverilog, "-g2012", "-s", "jpeg_dma_tb", "-o", str(simulation), str(converted)],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG DMA tests passed" in result.stdout
def test_jpeg_apb_ring_descriptor_path(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
multimedia = ROOT / "rtl/ip/multimedia"
source_list = tmp_path / "apb4_jpeg_ring.fl"
modules = [
"jpeg_pkg.sv",
"jpeg_transform.sv",
"jpeg_quantizer.sv",
"jpeg_entropy_encoder.sv",
"jpeg_entropy_decoder.sv",
"jpeg_bit_packer.sv",
"jpeg_bit_reader.sv",
"jpeg_byte_unpacker.sv",
"jpeg_byte_joiner.sv",
"jpeg_header_writer.sv",
"jpeg_marker_parser.sv",
"jpeg_table_store.sv",
"jpeg_table_cache.sv",
"jpeg_block_encoder.sv",
"jpeg_block_decoder.sv",
"jpeg_mcu_builder.sv",
"jpeg_mcu_reconstructor.sv",
"jpeg_encode_core.sv",
"jpeg_decode_core.sv",
"jpeg_dma.sv",
"jpeg_reg.sv",
"apb4_jpeg.sv",
]
source_list.write_text(
"\n".join(
[
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl/interface'}",
f"+incdir+{multimedia}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/apb4_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_stream_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/spill_register.sv"),
str(ROOT / "rtl/tech/tc_sram.sv"),
str(ROOT / "rtl/ip/peripheral/dma_axi4_master.sv"),
*(str(multimedia / module) for module in modules),
str(ROOT / "tests/rtl/apb4_jpeg_ring_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "apb4_jpeg_ring_tb.v"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "apb4_jpeg_ring_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
"-DPDK_BEHAV",
"-s",
"apb4_jpeg_ring_tb",
"-o",
str(simulation),
str(converted),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG APB ring tests passed" in result.stdout
def test_jpeg_table_store(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
simulation = tmp_path / "jpeg_table_store_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
"-DPDK_BEHAV",
"-I",
str(ROOT / "rtl/managed/clusterip/common/rtl"),
"-s",
"jpeg_table_store_tb",
"-o",
str(simulation),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/tech/tc_sram.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_table_store.sv"),
str(ROOT / "tests/rtl/jpeg_table_store_tb.sv"),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG table store tests passed" in result.stdout
def test_jpeg_entropy_decoder(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
simulation = tmp_path / "jpeg_entropy_decoder_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
"-I",
str(ROOT / "rtl/managed/clusterip/common/rtl"),
"-s",
"jpeg_entropy_decoder_tb",
"-o",
str(simulation),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_entropy_decoder.sv"),
str(ROOT / "tests/rtl/jpeg_entropy_decoder_tb.sv"),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG entropy decoder tests passed" in result.stdout
def test_jpeg_byte_unpacker_and_bit_reader(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
source_list = tmp_path / "jpeg_bit_reader.fl"
source_list.write_text(
"\n".join(
[
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_stream_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_byte_unpacker.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_bit_reader.sv"),
str(ROOT / "tests/rtl/jpeg_bit_reader_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "jpeg_bit_reader_tb.v"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "jpeg_bit_reader_tb"
subprocess.run(
[iverilog, "-g2012", "-s", "jpeg_bit_reader_tb", "-o", str(simulation),
str(converted)],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG byte unpacker and bit reader tests passed" in result.stdout
def test_jpeg_marker_parser(tmp_path: Path) -> None:
from jpeg_reference import (
AC_CHROMA_BITS,
AC_CHROMA_VALUES,
AC_LUMA_BITS,
AC_LUMA_VALUES,
DC_CHROMA_BITS,
DC_LUMA_BITS,
DC_VALUES,
Sampling,
_huffman_codes,
encode_rgb,
scaled_quant_table,
LUMA_QUANT,
CHROMA_QUANT,
)
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
encoded = encode_rgb(
bytes((index * 13) & 0xFF for index in range(17 * 15 * 3)),
17,
15,
quality=75,
sampling=Sampling.YUV420,
restart_interval=1,
)
hex_path = tmp_path / "jpeg_input.hex"
tables_path = tmp_path / "jpeg_tables.hex"
_write_hex_bytes(hex_path, encoded)
expected_tables = [0] * 3072
luma_quant = scaled_quant_table(LUMA_QUANT, 75)
chroma_quant = scaled_quant_table(CHROMA_QUANT, 75)
for index, value in enumerate(luma_quant):
expected_tables[index] = value
for index, value in enumerate(chroma_quant):
expected_tables[256 + index] = value
table_specs = (
(4, DC_LUMA_BITS, DC_VALUES),
(5, DC_CHROMA_BITS, DC_VALUES),
(8, AC_LUMA_BITS, AC_LUMA_VALUES),
(9, AC_CHROMA_BITS, AC_CHROMA_VALUES),
)
for kind, bits, values in table_specs:
for symbol, (code, length) in _huffman_codes(bits, values).items():
expected_tables[(kind * 256) + symbol] = (length << 16) | code
_write_hex_words(tables_path, expected_tables)
simulation = tmp_path / "jpeg_marker_parser_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
"-I",
str(ROOT / "rtl/managed/clusterip/common/rtl"),
"-s",
"jpeg_marker_parser_tb",
"-o",
str(simulation),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_marker_parser.sv"),
str(ROOT / "tests/rtl/jpeg_marker_parser_tb.sv"),
],
check=True,
)
result = subprocess.run(
[vvp, str(simulation), f"+jpeg_hex={hex_path}", f"+tables_hex={tables_path}",
f"+jpeg_size={len(encoded)}"],
text=True,
capture_output=True,
check=True,
)
assert "JPEG marker parser tests passed" in result.stdout
def test_jpeg_block_decoder_pipeline(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
simulation = tmp_path / "jpeg_block_decoder_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
"-I",
str(ROOT / "rtl/managed/clusterip/common/rtl"),
"-s",
"jpeg_block_decoder_tb",
"-o",
str(simulation),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_entropy_decoder.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_quantizer.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_transform.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_block_decoder.sv"),
str(ROOT / "tests/rtl/jpeg_block_decoder_tb.sv"),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG block decoder tests passed" in result.stdout
def test_jpeg_block_encoder_pipeline(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
simulation = tmp_path / "jpeg_block_encoder_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
"-I",
str(ROOT / "rtl/managed/clusterip/common/rtl"),
"-s",
"jpeg_block_encoder_tb",
"-o",
str(simulation),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_transform.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_quantizer.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_entropy_encoder.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_block_encoder.sv"),
str(ROOT / "tests/rtl/jpeg_block_encoder_tb.sv"),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG block encoder tests passed" in result.stdout
def test_jpeg_mcu_reconstructor(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
source_list = tmp_path / "jpeg_mcu_reconstructor.fl"
source_list.write_text(
"\n".join(
[
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_stream_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_mcu_reconstructor.sv"),
str(ROOT / "tests/rtl/jpeg_mcu_reconstructor_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "jpeg_mcu_reconstructor_tb.v"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "jpeg_mcu_reconstructor_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-s",
"jpeg_mcu_reconstructor_tb",
"-o",
str(simulation),
str(converted),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG MCU reconstructor tests passed" in result.stdout
def test_jpeg_mcu_builder(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
source_list = tmp_path / "jpeg_mcu_builder.fl"
source_list.write_text(
"\n".join(
[
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_stream_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_mcu_builder.sv"),
str(ROOT / "tests/rtl/jpeg_mcu_builder_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "jpeg_mcu_builder_tb.v"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "jpeg_mcu_builder_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-s",
"jpeg_mcu_builder_tb",
"-o",
str(simulation),
str(converted),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG MCU builder tests passed" in result.stdout
def test_jpeg_header_writer(tmp_path: Path) -> None:
from jpeg_reference import (
CHROMA_QUANT,
LUMA_QUANT,
Sampling,
_parse_header,
encode_rgb,
scaled_quant_table,
)
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
encoded = encode_rgb(
bytes((index * 13) & 0xFF for index in range(17 * 15 * 3)),
17,
15,
quality=75,
sampling=Sampling.YUV420,
restart_interval=1,
)
header = encoded[: _parse_header(encoded).entropy_offset]
luma = scaled_quant_table(LUMA_QUANT, 75)
chroma = scaled_quant_table(CHROMA_QUANT, 75)
header_path = tmp_path / "header.hex"
luma_path = tmp_path / "luma.hex"
chroma_path = tmp_path / "chroma.hex"
_write_hex_bytes(header_path, header)
_write_hex_bytes(luma_path, bytes(luma))
_write_hex_bytes(chroma_path, bytes(chroma))
simulation = tmp_path / "jpeg_header_writer_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
"-I",
str(ROOT / "rtl/managed/clusterip/common/rtl"),
"-s",
"jpeg_header_writer_tb",
"-o",
str(simulation),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_header_writer.sv"),
str(ROOT / "tests/rtl/jpeg_header_writer_tb.sv"),
],
check=True,
)
result = subprocess.run(
[
vvp,
str(simulation),
f"+header_hex={header_path}",
f"+luma_hex={luma_path}",
f"+chroma_hex={chroma_path}",
f"+header_size={len(header)}",
],
text=True,
capture_output=True,
check=True,
)
assert "JPEG header writer tests passed" in result.stdout
def test_jpeg_byte_joiner(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
source_list = tmp_path / "jpeg_byte_joiner.fl"
source_list.write_text(
"\n".join(
[
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_stream_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_byte_joiner.sv"),
str(ROOT / "tests/rtl/jpeg_byte_joiner_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "jpeg_byte_joiner_tb.v"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "jpeg_byte_joiner_tb"
subprocess.run(
[iverilog, "-g2012", "-s", "jpeg_byte_joiner_tb", "-o", str(simulation),
str(converted)],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "JPEG byte joiner tests passed" in result.stdout
def test_jpeg_encode_core_complete_file(tmp_path: Path) -> None:
from jpeg_reference import (
AC_CHROMA_BITS,
AC_CHROMA_VALUES,
AC_LUMA_BITS,
AC_LUMA_VALUES,
CHROMA_QUANT,
DC_CHROMA_BITS,
DC_LUMA_BITS,
DC_VALUES,
LUMA_QUANT,
Sampling,
_huffman_codes,
encode_rgb,
scaled_quant_table,
)
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
def entries(bits: tuple[int, ...], values: tuple[int, ...], size: int) -> list[int]:
codes = _huffman_codes(bits, values)
return [((codes[symbol][1] << 16) | codes[symbol][0]) if symbol in codes else 0
for symbol in range(size)]
rgb = bytes([128] * (16 * 16 * 3))
expected = encode_rgb(rgb, 16, 16, quality=75, sampling=Sampling.YUV420)
files = {
"expected": tmp_path / "expected.hex",
"luma_quant": tmp_path / "luma_quant.hex",
"chroma_quant": tmp_path / "chroma_quant.hex",
"luma_dc": tmp_path / "luma_dc.hex",
"chroma_dc": tmp_path / "chroma_dc.hex",
"luma_ac": tmp_path / "luma_ac.hex",
"chroma_ac": tmp_path / "chroma_ac.hex",
}
_write_hex_bytes(files["expected"], expected)
_write_hex_bytes(files["luma_quant"], bytes(scaled_quant_table(LUMA_QUANT, 75)))
_write_hex_bytes(files["chroma_quant"], bytes(scaled_quant_table(CHROMA_QUANT, 75)))
_write_hex_words(files["luma_dc"], entries(DC_LUMA_BITS, DC_VALUES, 12))
_write_hex_words(files["chroma_dc"], entries(DC_CHROMA_BITS, DC_VALUES, 12))
_write_hex_words(files["luma_ac"], entries(AC_LUMA_BITS, AC_LUMA_VALUES, 256))
_write_hex_words(files["chroma_ac"], entries(AC_CHROMA_BITS, AC_CHROMA_VALUES, 256))
source_list = tmp_path / "jpeg_encode_core.fl"
source_list.write_text(
"\n".join(
[
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_stream_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/spill_register.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_transform.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_quantizer.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_entropy_encoder.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_block_encoder.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_bit_packer.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_header_writer.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_mcu_builder.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_byte_joiner.sv"),
str(ROOT / "rtl/ip/multimedia/jpeg_encode_core.sv"),
str(ROOT / "tests/rtl/jpeg_encode_core_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "jpeg_encode_core_tb.v"
subprocess.run(
[sys.executable, str(ROOT / "rtl/mini/script/convt_sv2v.py"), "-f", str(source_list),
"--output", str(converted)],
check=True,
)
simulation = tmp_path / "jpeg_encode_core_tb"
subprocess.run(
[iverilog, "-g2012", "-s", "jpeg_encode_core_tb", "-o", str(simulation),
str(converted)],
check=True,
)
plusargs = [f"+{name}_hex={path}" for name, path in files.items()]
result = subprocess.run(
[vvp, str(simulation), *plusargs, f"+expected_size={len(expected)}"],
text=True,
capture_output=True,
check=True,
)
assert "JPEG encode core tests passed" in result.stdout
baseline_match = re.search(r"cycles=(\d+)", result.stdout)
assert baseline_match is not None
baseline_cycles = int(baseline_match.group(1))
benchmark_rgb = bytes([128] * (16 * 32 * 3))
benchmark = encode_rgb(benchmark_rgb, 16, 32, quality=75, sampling=Sampling.YUV420)
_write_hex_bytes(files["expected"], benchmark)
result = subprocess.run(
[
vvp,
str(simulation),
*plusargs,
f"+expected_size={len(benchmark)}",
"+test_width=16",
"+test_height=32",
"+input_beats=192",
],
text=True,
capture_output=True,
check=True,
)
match = re.search(r"cycles=(\d+)", result.stdout)
assert match is not None
benchmark_cycles = int(match.group(1))
assert baseline_cycles < benchmark_cycles
assert benchmark_cycles - baseline_cycles < 1_000
def test_jpeg_decode_core_complete_file(tmp_path: Path) -> None:
from jpeg_reference import Sampling, decode_rgb, encode_rgb
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
rgb = bytes([128] * (16 * 16 * 3))
encoded = encode_rgb(rgb, 16, 16, quality=75, sampling=Sampling.YUV420)
expected = decode_rgb(encoded).rgb
input_path = tmp_path / "input.hex"
expected_path = tmp_path / "expected_rgb.hex"
_write_hex_bytes(input_path, encoded)
_write_hex_bytes(expected_path, expected)
source_list = tmp_path / "jpeg_decode_core.fl"
sources = [
ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_stream_if.sv",
ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv",
ROOT / "rtl/tech/tc_sram.sv",
ROOT / "rtl/ip/multimedia/jpeg_byte_unpacker.sv",
ROOT / "rtl/ip/multimedia/jpeg_marker_parser.sv",
ROOT / "rtl/ip/multimedia/jpeg_table_store.sv",
ROOT / "rtl/ip/multimedia/jpeg_table_cache.sv",
ROOT / "rtl/ip/multimedia/jpeg_bit_reader.sv",
ROOT / "rtl/ip/multimedia/jpeg_entropy_decoder.sv",
ROOT / "rtl/ip/multimedia/jpeg_quantizer.sv",
ROOT / "rtl/ip/multimedia/jpeg_transform.sv",
ROOT / "rtl/ip/multimedia/jpeg_block_decoder.sv",
ROOT / "rtl/ip/multimedia/jpeg_mcu_reconstructor.sv",
ROOT / "rtl/ip/multimedia/jpeg_decode_core.sv",
ROOT / "tests/rtl/jpeg_decode_core_tb.sv",
]
source_list.write_text(
"\n".join(
[
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
"+define+PDK_BEHAV",
*(str(source) for source in sources),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "jpeg_decode_core_tb.v"
subprocess.run(
[sys.executable, str(ROOT / "rtl/mini/script/convt_sv2v.py"), "-f", str(source_list),
"--output", str(converted)],
check=True,
)
simulation = tmp_path / "jpeg_decode_core_tb"
subprocess.run(
[iverilog, "-g2012", "-s", "jpeg_decode_core_tb", "-o", str(simulation),
str(converted)],
check=True,
)
result = subprocess.run(
[
vvp,
str(simulation),
f"+input_hex={input_path}",
f"+input_size={len(encoded)}",
f"+expected_rgb_hex={expected_path}",
f"+expected_rgb_size={len(expected)}",
],
text=True,
capture_output=True,
check=True,
)
assert "JPEG decode core tests passed" in result.stdout
+99
View File
@@ -0,0 +1,99 @@
"""Tests for the deterministic baseline JPEG reference model."""
from __future__ import annotations
import io
import pytest
from jpeg_reference import (
JpegModelError,
Sampling,
decode_rgb,
encode_rgb,
fdct,
idct,
scaled_quant_table,
LUMA_QUANT,
)
def _pattern(width: int, height: int) -> bytes:
data = bytearray()
for y_pos in range(height):
for x_pos in range(width):
data.extend(
(
(x_pos * 17 + y_pos * 3) & 0xFF,
(x_pos * 5 + y_pos * 19) & 0xFF,
(x_pos * 11 + y_pos * 7) & 0xFF,
)
)
return bytes(data)
def test_constant_block_transform_round_trip() -> None:
coefficients = fdct([1] * 64)
assert coefficients[0] == 8
assert coefficients[1:] == (0,) * 63
assert idct(coefficients) == (1,) * 64
def test_quality_scaling_bounds() -> None:
assert scaled_quant_table(LUMA_QUANT, 50) == LUMA_QUANT
assert scaled_quant_table(LUMA_QUANT, 100) == (1,) * 64
assert max(scaled_quant_table(LUMA_QUANT, 1)) == 255
with pytest.raises(ValueError, match="1..100"):
scaled_quant_table(LUMA_QUANT, 0)
@pytest.mark.parametrize(
"sampling",
[Sampling.GRAY, Sampling.YUV444, Sampling.YUV422, Sampling.YUV420],
)
@pytest.mark.parametrize("dimensions", [(1, 1), (8, 8), (17, 15), (31, 19)])
def test_baseline_round_trip(sampling: Sampling, dimensions: tuple[int, int]) -> None:
width, height = dimensions
encoded = encode_rgb(_pattern(width, height), width, height, quality=90, sampling=sampling)
decoded = decode_rgb(encoded)
assert decoded.width == width
assert decoded.height == height
assert decoded.sampling == sampling
assert len(decoded.rgb) == width * height * 3
assert encoded[:2] == b"\xff\xd8"
assert encoded[-2:] == b"\xff\xd9"
def test_restart_markers_round_trip() -> None:
encoded = encode_rgb(
_pattern(33, 25),
33,
25,
quality=80,
sampling=Sampling.YUV420,
restart_interval=1,
)
assert b"\xff\xd0" in encoded
assert b"\xff\xd1" in encoded
decoded = decode_rgb(encoded)
assert (decoded.width, decoded.height) == (33, 25)
def test_dimension_and_process_rejections() -> None:
encoded = encode_rgb(_pattern(8, 8), 8, 8)
with pytest.raises(JpegModelError, match="dimension limit"):
decode_rgb(encoded, max_dimension=7)
progressive = encoded.replace(b"\xff\xc0", b"\xff\xc2", 1)
with pytest.raises(JpegModelError, match="unsupported JPEG frame process"):
decode_rgb(progressive)
with pytest.raises(JpegModelError):
decode_rgb(encoded[:-3])
def test_stream_is_accepted_by_pillow_when_available() -> None:
pillow = pytest.importorskip("PIL.Image")
encoded = encode_rgb(_pattern(19, 13), 19, 13, quality=85, sampling=Sampling.YUV420)
image = pillow.open(io.BytesIO(encoded))
image.load()
assert image.size == (19, 13)
assert image.mode == "RGB"
+121
View File
@@ -0,0 +1,121 @@
"""Keep the handwritten JPEG RTL and SDK register constants synchronized."""
from __future__ import annotations
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
RTL_DEFINE = ROOT / "rtl/ip/multimedia/jpeg_define.svh"
C_DEFINE = ROOT / "crt/include/retrosoc/hal/jpeg_regs.h"
def _rtl_values() -> dict[str, int]:
values: dict[str, int] = {}
pattern = re.compile(r"^`define\s+(\w+)\s+((?:\d+)'[hHdD][0-9a-fA-F]+|\d+)\s*$")
for line in RTL_DEFINE.read_text(encoding="utf-8").splitlines():
match = pattern.match(line)
if match is None:
continue
name, literal = match.groups()
if "'" in literal:
_, value = literal.split("'", maxsplit=1)
base = 16 if value[0].lower() == "h" else 10
values[name] = int(value[1:], base)
else:
values[name] = int(literal, 10)
return values
def _c_values() -> dict[str, int]:
values: dict[str, int] = {}
pattern = re.compile(r"^#define\s+(\w+)\s+(?:UINT32_C\((0x[0-9a-fA-F]+)\)|(\d+)U)\s*$")
for line in C_DEFINE.read_text(encoding="utf-8").splitlines():
match = pattern.match(line)
if match is None:
continue
name, hexadecimal, decimal = match.groups()
values[name] = int(hexadecimal if hexadecimal is not None else decimal, 0)
return values
def test_jpeg_register_offsets_match_rtl() -> None:
rtl = _rtl_values()
c = _c_values()
rtl_offsets = {name: value for name, value in rtl.items() if name.startswith("APB4_JPEG__")}
excluded = {
name
for name in rtl_offsets
if any(
marker in name
for marker in (
"COMMAND_START",
"COMMAND_ABORT",
"COMMAND_SOFT_RESET",
"COMMAND_RING_KICK",
"STATUS_BUSY",
"STATUS_RING_ACTIVE",
"STATUS_ENCODE",
"STATUS_IDLE",
"IRQ_JOB_DONE",
"IRQ_RING_EVENT",
"IRQ_HEADER_READY",
"IRQ_ABORT_DONE",
"IRQ_ERROR",
"JOB_CONFIG_",
"DESCRIPTOR_",
"RING_CONTROL_",
"RING_STATUS_",
"TABLE_COMMAND_",
)
)
}
for rtl_name, rtl_value in rtl_offsets.items():
if rtl_name in excluded:
continue
c_name = rtl_name.replace("APB4_JPEG__", "RS_JPEG_REG_")
assert c[c_name] == rtl_value, f"{rtl_name} != {c_name}"
def test_jpeg_register_fields_match_rtl() -> None:
rtl = _rtl_values()
c = _c_values()
bit_mapping = {
"APB4_JPEG__COMMAND_START": "RS_JPEG_COMMAND_START",
"APB4_JPEG__COMMAND_ABORT": "RS_JPEG_COMMAND_ABORT",
"APB4_JPEG__COMMAND_SOFT_RESET": "RS_JPEG_COMMAND_SOFT_RESET",
"APB4_JPEG__COMMAND_RING_KICK": "RS_JPEG_COMMAND_RING_KICK",
"APB4_JPEG__IRQ_JOB_DONE": "RS_JPEG_IRQ_JOB_DONE",
"APB4_JPEG__IRQ_RING_EVENT": "RS_JPEG_IRQ_RING_EVENT",
"APB4_JPEG__IRQ_HEADER_READY": "RS_JPEG_IRQ_HEADER_READY",
"APB4_JPEG__IRQ_ABORT_DONE": "RS_JPEG_IRQ_ABORT_DONE",
"APB4_JPEG__IRQ_ERROR": "RS_JPEG_IRQ_ERROR",
"APB4_JPEG__DESCRIPTOR_OWN": "RS_JPEG_DESCRIPTOR_OWN",
"APB4_JPEG__DESCRIPTOR_IOC": "RS_JPEG_DESCRIPTOR_IOC",
"APB4_JPEG__DESCRIPTOR_ENCODE": "RS_JPEG_DESCRIPTOR_ENCODE",
"APB4_JPEG__DESCRIPTOR_AUTO_HEADER": "RS_JPEG_DESCRIPTOR_AUTO_HEADER",
"APB4_JPEG__DESCRIPTOR_STRICT": "RS_JPEG_DESCRIPTOR_STRICT",
"APB4_JPEG__DESCRIPTOR_METADATA": "RS_JPEG_DESCRIPTOR_METADATA",
"APB4_JPEG__RING_CONTROL_ENABLE": "RS_JPEG_RING_CONTROL_ENABLE",
"APB4_JPEG__RING_CONTROL_STOP_ERR": "RS_JPEG_RING_CONTROL_STOP_ERROR",
"APB4_JPEG__RING_STATUS_ACTIVE": "RS_JPEG_RING_STATUS_ACTIVE",
"APB4_JPEG__RING_STATUS_EMPTY": "RS_JPEG_RING_STATUS_EMPTY",
"APB4_JPEG__RING_STATUS_STALLED": "RS_JPEG_RING_STATUS_STALLED",
"APB4_JPEG__RING_STATUS_ERROR": "RS_JPEG_RING_STATUS_ERROR",
"APB4_JPEG__TABLE_COMMAND_COMMIT": "RS_JPEG_TABLE_COMMAND_COMMIT",
"APB4_JPEG__TABLE_COMMAND_DEFAULT": "RS_JPEG_TABLE_COMMAND_DEFAULT",
"APB4_JPEG__TABLE_COMMAND_CLEAR": "RS_JPEG_TABLE_COMMAND_CLEAR",
}
for rtl_name, c_name in bit_mapping.items():
assert c[c_name] == 1 << rtl[rtl_name], f"{rtl_name} != {c_name}"
shift_mapping = {
"APB4_JPEG__JOB_CONFIG_TABLE": "RS_JPEG_JOB_CONFIG_TABLE_SHIFT",
"APB4_JPEG__DESCRIPTOR_TABLE": "RS_JPEG_DESCRIPTOR_TABLE_SHIFT",
"APB4_JPEG__DESCRIPTOR_INPUT_FMT": "RS_JPEG_DESCRIPTOR_INPUT_SHIFT",
"APB4_JPEG__DESCRIPTOR_OUTPUT_FMT": "RS_JPEG_DESCRIPTOR_OUTPUT_SHIFT",
"APB4_JPEG__DESCRIPTOR_SAMPLING": "RS_JPEG_DESCRIPTOR_SAMPLE_SHIFT",
}
for rtl_name, c_name in shift_mapping.items():
assert c[c_name] == rtl[rtl_name], f"{rtl_name} != {c_name}"
+7 -2
View File
@@ -77,7 +77,7 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
data_policy = (tmp_path / "rtl/soc_data_policy.svh").read_text(encoding="utf-8")
filelist = (tmp_path / "soc_topology.fl").read_text(encoding="utf-8")
assert interfaces.count("apb4_if u_") == 24
assert interfaces.count("apb4_if u_") == 25
assert "nmi_if" not in interfaces
assert "soc_nmi" not in interfaces
assert "assign s_psel_comb[17] = `SOC_ADDR_IS_APB4_I2C1(s_decode_addr);" in routes
@@ -92,6 +92,7 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
assert "assign s_psel_comb[23] = `SOC_ADDR_IS_APB4_HP_MAILBOX(s_decode_addr);" in routes
assert "assign s_psel_comb[24] = `SOC_ADDR_IS_HP_ACLINT(s_decode_addr);" in routes
assert "assign s_psel_comb[25] = `SOC_ADDR_IS_HP_PLIC(s_decode_addr);" in routes
assert "assign s_psel_comb[26] = `SOC_ADDR_IS_APB4_JPEG(s_decode_addr);" in routes
assert gpio.count("// GPIO") == 64
assert "u_uart1_if" not in gpio
assert "assign u_uart0_if.cts_n_i = u_gpio_if.di_i[0];" in gpio
@@ -140,7 +141,7 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
assert ".usb2_axi4(u_usb2_axi4_if)" in apb_periph_fabric
assert "`define SOC_IRQ_VECTOR_WIDTH 32" in irq_config
assert "`define SOC_USER_IRQ_MASK 32'h004EFBFC" in irq_config
assert "`define SOC_IRQ_APB4_PERIPH_WIDTH 22" in irq_config
assert "`define SOC_IRQ_APB4_PERIPH_WIDTH 23" in irq_config
assert "`define SOC_IRQ_APB4_SYSTEM_WIDTH 8" in irq_config
assert "assign irq_o[0] = u_clint_if.software_irq_o[0];" in rib_irq
assert "assign irq_o[10] = ws2812.irq_o;" in rib_irq
@@ -153,6 +154,7 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
assert "assign irq_o[17] = resource_irq_lp_i[3];" in rib_irq
assert "assign irq_o[18] = s_crypto_irq;" in rib_irq
assert "assign irq_o[19] = resource_irq_lp_i[1];" in rib_irq
assert "assign irq_o[22] = resource_irq_lp_i[5];" in rib_irq
assert "assign irq_o[0] = pwm.irq_o;" in apb_irq
assert "assign irq_o[4] = s_rng_irq;" in apb_irq
assert "assign irq_o[5] = s_ext_l_irq;" in apb_irq
@@ -169,6 +171,7 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
assert "s_irq[27] = s_apb4_system_irq[5];" in irq_wiring
assert "s_irq[28] = s_apb4_system_irq[6];" in irq_wiring
assert "s_irq[29] = s_apb4_system_irq[7];" in irq_wiring
assert "s_irq[30] = s_apb4_periph_irq[22];" in irq_wiring
assert "irq_i[10] == apb4_periph_irq_i[16]" in irq_sva
assert "irq_i[15] == apb4_periph_irq_i[13]" in irq_sva
assert "irq_i[20] == apb4_periph_irq_i[14]" in irq_sva
@@ -176,6 +179,7 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
assert "irq_i[21] == apb4_periph_irq_i[17]" in irq_sva
assert "irq_i[23] == apb4_periph_irq_i[18]" in irq_sva
assert "irq_i[24] == apb4_periph_irq_i[19]" in irq_sva
assert "irq_i[30] == apb4_periph_irq_i[22]" in irq_sva
assert "irq_i[31] == 1'b0" in irq_sva
assert "bind retrosoc soc_irq_topology_sva" in irq_sva
assert ".clk_i(clk_lp_i)" in irq_sva
@@ -230,6 +234,7 @@ def test_topology_preserves_default_irq_compatibility_mapping() -> None:
("uart1", "apb4_periph", 21, 26, "uart1.irq_o"),
("extension_l", "apb4_system", 5, 27, "s_ext_l_irq"),
("extension_h", "apb4_system", 6, 28, "s_ext_h_irq"),
("jpeg", "apb4_periph", 22, 30, "resource_irq_lp_i[5]"),
("resource_fault", "apb4_system", 7, 29, "s_resource_fault_irq"),
]