mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
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:
75 files changed
+15048
-58
No files matched your search
+9
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Vendored
+5
@@ -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.
|
||||
Vendored
+6
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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}"
|
||||
@@ -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"),
|
||||
]
|
||||
|
||||
|
||||
Reference in new issue
Block a user