more IRQ sources + AUX data channel

This commit is contained in:
Mateusz Faderewski 2024-08-05 22:16:46 +02:00
parent 5b986f9877
commit 598a4205bb
16 changed files with 415 additions and 81 deletions

View File

@ -8,6 +8,7 @@
- [`0x1FFF_000C`: **IDENTIFIER**](#0x1fff_000c-identifier)
- [`0x1FFF_0010`: **KEY**](#0x1fff_0010-key)
- [`0x1FFF_0014`: **IRQ**](#0x1fff_0014-irq)
- [`0x1FFF_0018`: **AUX**](#0x1fff_0018-aux)
- [Command execution flow](#command-execution-flow)
- [Without interrupt](#without-interrupt)
- [With interrupt](#with-interrupt)
@ -30,7 +31,7 @@ This mapping is used internally by FPGA/μC and when accessing flashcart from US
- Note [1]: Flash memory region `0x04E0_0000` - `0x04FD_FFFF` is write protected as it contains N64 bootloader. This section can be overwritten only via firmware update process.
- Note [2]: Due to BlockRAM usage optimization this section is read only.
- Note [3]: Read returns `0`. Maximum accessibe address space is 128 MiB.
- Note [3]: Read returns `0`. Maximum accessible address space is 128 MiB.
---
@ -54,11 +55,11 @@ This mapping is used when accessing flashcart from N64 side.
| EEPROM | `0x1FFE_2000` | 2 kiB | RW | `0x0500_2000` | Block RAM | mem bus | SC64 register access is enabled |
| 64DD buffer [8] | `0x1FFE_2800` | 256 bytes | RW | `0x0500_2800` | Block RAM | mem bus | SC64 register access is enabled |
| FlashRAM buffer [8] | `0x1FFE_2900` | 128 bytes | R | `0x0500_2900` | Block RAM | mem bus | SC64 register access is enabled |
| SC64 registers | `0x1FFF_0000` | 24 bytes | RW | N/A | Flashcart Interface | reg bus | SC64 register access is enabled |
| SC64 registers | `0x1FFF_0000` | 28 bytes | RW | N/A | Flashcart Interface | reg bus | SC64 register access is enabled |
- Note [1]: 64DD IPL share SDRAM memory space with ROM (last 4 MiB minus 128 kiB for saves). Write access is always disabled for this section.
- Note [2]: SRAM and FlashRAM save types share SDRAM memory space with ROM (last 128 kiB).
- Note [3]: 32 kiB chunks are accesed at `0x0800_0000`, `0x0804_0000` and `0x0808_0000`.
- Note [3]: 32 kiB chunks are accessed at `0x0800_0000`, `0x0804_0000` and `0x0808_0000`.
- Note [4]: FlashRAM read access is multiplexed between mem and reg bus, writes are always mapped to reg bus.
- Note [5]: Write access is available when `ROM_WRITE_ENABLE` config is enabled.
- Note [6]: This address overlaps last 128 kiB of ROM space allowing SRAM and FlashRAM save types to work with games occupying almost all of ROM space (for example Pokemon Stadium 2). Reads are redirected to last 128 kiB of flash.
@ -88,7 +89,7 @@ Data read will be corrupted and erase/program operations slows down.
## SC64 registers
SC64 contains small register region used for communication between N64 and controller code running on the μC.
Protocol is command based with support for up to 256 diferrent commands and two 32-bit argument/result values per operation.
Protocol is command based with support for up to 256 different commands and two 32-bit argument/result values per operation.
Support for interrupts is provided but currently no command relies on it, 64DD IRQ is handled separately.
| name | address | size | access | usage |
@ -98,19 +99,22 @@ Support for interrupts is provided but currently no command relies on it, 64DD I
| **DATA1** | `0x1FFF_0008` | 4 bytes | RW | Command argument/result 1 |
| **IDENTIFIER** | `0x1FFF_000C` | 4 bytes | RW | Flashcart identifier |
| **KEY** | `0x1FFF_0010` | 4 bytes | W | SC64 register access lock/unlock |
| **IRQ** | `0x1FFF_0014` | 4 bytes | W | Pending IRQ clear |
| **IRQ** | `0x1FFF_0014` | 4 bytes | RW | IRQ clear and enable |
| **AUX** | `0x1FFF_0018` | 4 bytes | RW | Auxiliary interrupt data channel |
---
#### `0x1FFF_0000`: **STATUS/COMMAND**
#### `0x1FFF_0000`: **STATUS/COMMAND**
| name | bits | access | meaning |
| ----------------- | ------ | ------ | ----------------------------------------------------------- |
| `CMD_BUSY` | [31] | R | `1` if dispatched command is pending/executing |
| `CMD_ERROR` | [30] | R | `1` if last executed command returned with error code |
| `MCU_IRQ_PENDING` | [29] | R | `1` if flashcart has raised a MCU interrupt |
| `CMD_IRQ_PENDING` | [28] | R | `1` if flashcart has raised a command finish interrupt |
| N/A | [27:9] | N/A | Unused, write `0` for future compatibility |
| `BTN_IRQ_PENDING` | [29] | R | `1` if flashcart has raised a "button pressed" interrupt |
| `CMD_IRQ_PENDING` | [28] | R | `1` if flashcart has raised a "command finish" interrupt |
| `USB_IRQ_PENDING` | [27] | R | `1` if flashcart has raised au "USB not empty" interrupt |
| `AUX_IRQ_PENDING` | [26] | R | `1` if flashcart has raised an "AUX not empty" interrupt |
| N/A | [25:9] | N/A | Unused, write `0` for future compatibility |
| `CMD_IRQ_REQUEST` | [8] | RW | Raise cart interrupt signal when command finishes execution |
| `CMD_ID` | [7:0] | RW | Command ID to be executed |
@ -145,20 +149,54 @@ Note: Result is valid only when command has executed and `CMD_BUSY` bit is reset
Note: By default from cold boot (power on) or console reset (NMI) flashcart will disable access to SC64 specific memory regions.
**KEY** register is always enabled and listening for writes regardless of lock/unlock state.
To enable SC64 registers it is necesarry to provide sequence of values to this register.
To enable SC64 registers it is necessary to provide sequence of values to this register.
Value `0x00000000` will reset sequencer state.
Two consequentive writes of values `0x5F554E4C` and `0x4F434B5F` will unlock all SC64 registers if flashcart is in lock state.
Two consecutive writes of values `0x5F554E4C` and `0x4F434B5F` will unlock all SC64 registers if flashcart is in lock state.
Value `0xFFFFFFFF` will lock all SC64 registers if flashcart is in unlock state.
---
#### `0x1FFF_0014`: **IRQ**
| name | bits | access | meaning |
| ----------- | ------ | ------ | --------------------------------------------- |
| `MCU_CLEAR` | [31] | W | Write `1` to clear a MCU interrupt |
| `CMD_CLEAR` | [30] | W | Write `1` to clear a command finish interrupt |
| N/A | [29:0] | N/A | Unused, write `0` for future compatibility |
| name | bits | access | meaning |
| ----------------- | ------- | ------ | ----------------------------------------------------------------- |
| `BTN_CLEAR` | [31] | W | Write `1` to clear "button pressed" interrupt |
| `CMD_CLEAR` | [30] | W | Write `1` to clear "command finish" interrupt |
| `USB_CLEAR` | [29] | W | Write `1` to clear "USB not empty" interrupt |
| `AUX_CLEAR` | [28] | W | Write `1` to clear "AUX not empty" interrupt |
| N/A | [27:24] | N/A | Unused, write `0` for future compatibility |
| `MCU_IRQ_MASK` | [23] | R | `1` means "button pressed" interrupt is enabled (it's always `1`) |
| `CMD_IRQ_MASK` | [22] | R | `1` means "command finish" interrupt is enabled (it's always `1`) |
| `USB_IRQ_MASK` | [21] | R | `1` means "USB not empty" interrupt is enabled |
| `AUX_IRQ_MASK` | [20] | R | `1` means "AUX not empty" interrupt is enabled |
| N/A | [19:16] | N/A | Unused, write `0` for future compatibility |
| `USB_IRQ_DISABLE` | [11] | W | Write `1` to disable "USB not empty" interrupt |
| `USB_IRQ_ENABLE` | [10] | W | Write `1` to enable "USB not empty" interrupt |
| `AUX_IRQ_DISABLE` | [9] | W | Write `1` to disable "AUX not empty" interrupt |
| `AUX_IRQ_ENABLE` | [8] | W | Write `1` to enable "AUX not empty" interrupt |
| N/A | [7:0] | N/A | Unused, write `0` for future compatibility |
Note: All interrupts are cleared and disabled when any of the following events occur:
- Hard reset
- NMI reset
- SC64 registers locking
SC64 interrupts are completely disabled when register access is not enabled.
---
#### `0x1FFF_0018`: **AUX**
This register can be used as a very simple interface to the PC via USB.
Writing to this register generates an USB transfer with the contents of the written data.
New data available in the register is signaled via cart interrupt that needs to be enabled beforehand in the `IRQ` register.
There is no flow control, use this register as a ping-pong interface.
For example, PC sends AUX data, N64 receives interrupt, reads the data then writes to this register with a response.
This flow can be reversed if needed - N64 can be the initiating side.
| name | bits | access | meaning |
| ------ | ------ | ------ | -------------- |
| `DATA` | [31:0] | RW | Arbitrary data |
---

View File

@ -43,13 +43,17 @@
- [`arg0` (type)](#arg0-type)
- [`arg1` (length)](#arg1-length-2)
- [`data` (data)](#data-data-1)
- [`X`: **AUX\_WRITE**](#x-aux_write)
- [`arg0` (data)](#arg0-data)
- [`D`: **DD\_SET\_BLOCK\_READY**](#d-dd_set_block_ready)
- [`arg0` (error)](#arg0-error)
- [`W`: **WRITEBACK\_ENABLE**](#w-writeback_enable)
- [Asynchronous packets](#asynchronous-packets)
- [`X`: **AUX\_DATA**](#x-aux_data)
- [`data` (data)](#data-data-2)
- [`B`: **BUTTON**](#b-button)
- [`U`: **DATA**](#u-data)
- [`data` (data)](#data-data-2)
- [`data` (data)](#data-data-3)
- [`G`: **DATA\_FLUSHED**](#g-data_flushed)
- [`D`: **DISK\_REQUEST**](#d-disk_request)
- [`data` (disk\_info/block\_data)](#data-disk_infoblock_data)
@ -148,29 +152,30 @@ Available packet IDs are listed in the [asynchronous packets](#asynchronous-pack
## Supported commands
| id | name | arg0 | arg1 | data | response | description |
| --- | ----------------------------------------------- | ------------ | ------------- | ---- | ---------------- | ------------------------------------------------------------- |
| `v` | [**IDENTIFIER_GET**](#v-identifier_get) | --- | --- | --- | identifier | Get flashcart identifier `SCv2` |
| `V` | [**VERSION_GET**](#v-version_get) | --- | --- | --- | version | Get flashcart firmware version |
| `R` | [**STATE_RESET**](#r-state_reset) | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) |
| `B` | [**CIC_PARAMS_SET**](#b-cic_params_set) | cic_params_0 | cic_params_1 | --- | --- | Set CIC emulation parameters (disable/seed/checksum) |
| `c` | [**CONFIG_GET**](#c-config_get) | config_id | --- | --- | config_value | Get config option |
| `C` | [**CONFIG_SET**](#c-config_set) | config_id | config_value | --- | --- | Set config option |
| `a` | [**SETTING_GET**](#a-setting_get) | setting_id | --- | --- | setting_value | Get persistent setting option |
| `A` | [**SETTING_SET**](#a-setting_set) | setting_id | setting_value | --- | --- | Set persistent setting option |
| `t` | [**TIME_GET**](#t-time_get) | --- | --- | --- | time | Get current RTC value |
| `T` | [**TIME_SET**](#t-time_set) | time_0 | time_1 | --- | --- | Set new RTC value |
| `m` | [**MEMORY_READ**](#m-memory_read) | address | length | --- | data | Read data from specified memory address |
| `M` | [**MEMORY_WRITE**](#m-memory_write) | address | length | data | --- | Write data to specified memory address |
| `U` | [**USB_WRITE**](#u-usb_write) | type | length | data | N/A | Send data to be received by app running on N64 (no response!) |
| `D` | [**DD_SET_BLOCK_READY**](#d-dd_set_block_ready) | error | --- | --- | --- | Notify flashcart about 64DD block readiness |
| `W` | [**WRITEBACK_ENABLE**](#w-writeback_enable) | --- | --- | --- | --- | Enable save writeback through USB packet |
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size |
| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase |
| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address |
| `F` | **FIRMWARE_UPDATE** | address | length | --- | status | Update firmware from specified memory address |
| `?` | **DEBUG_GET** | --- | --- | --- | debug_data | Get internal FPGA debug info |
| `%` | **DIAGNOSTIC_GET** | --- | --- | --- | diagnostic_data | Get diagnostic data |
| id | name | arg0 | arg1 | data | response | description |
| --- | ----------------------------------------------- | ------------ | ------------- | ---- | ---------------- | -------------------------------------------------------------- |
| `v` | [**IDENTIFIER_GET**](#v-identifier_get) | --- | --- | --- | identifier | Get flashcart identifier `SCv2` |
| `V` | [**VERSION_GET**](#v-version_get) | --- | --- | --- | version | Get flashcart firmware version |
| `R` | [**STATE_RESET**](#r-state_reset) | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) |
| `B` | [**CIC_PARAMS_SET**](#b-cic_params_set) | cic_params_0 | cic_params_1 | --- | --- | Set CIC emulation parameters (disable/seed/checksum) |
| `c` | [**CONFIG_GET**](#c-config_get) | config_id | --- | --- | config_value | Get config option |
| `C` | [**CONFIG_SET**](#c-config_set) | config_id | config_value | --- | --- | Set config option |
| `a` | [**SETTING_GET**](#a-setting_get) | setting_id | --- | --- | setting_value | Get persistent setting option |
| `A` | [**SETTING_SET**](#a-setting_set) | setting_id | setting_value | --- | --- | Set persistent setting option |
| `t` | [**TIME_GET**](#t-time_get) | --- | --- | --- | time | Get current RTC value |
| `T` | [**TIME_SET**](#t-time_set) | time_0 | time_1 | --- | --- | Set new RTC value |
| `m` | [**MEMORY_READ**](#m-memory_read) | address | length | --- | data | Read data from specified memory address |
| `M` | [**MEMORY_WRITE**](#m-memory_write) | address | length | data | --- | Write data to specified memory address |
| `U` | [**USB_WRITE**](#u-usb_write) | type | length | data | N/A | Send data to be received by app running on N64 (no response!) |
| `X` | [**AUX_WRITE**](#x-aux_write) | data | --- | --- | --- | Send small auxiliary data to be received by app running on N64 |
| `D` | [**DD_SET_BLOCK_READY**](#d-dd_set_block_ready) | error | --- | --- | --- | Notify flashcart about 64DD block readiness |
| `W` | [**WRITEBACK_ENABLE**](#w-writeback_enable) | --- | --- | --- | --- | Enable save writeback through USB packet |
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size |
| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase |
| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address |
| `F` | **FIRMWARE_UPDATE** | address | length | --- | status | Update firmware from specified memory address |
| `?` | **DEBUG_GET** | --- | --- | --- | debug_data | Get internal FPGA debug info |
| `%` | **DIAGNOSTIC_GET** | --- | --- | --- | diagnostic_data | Get diagnostic data |
---
@ -447,6 +452,21 @@ If N64 acknowledge the request, then data is written to the flashcart memory to
---
### `X`: **AUX_WRITE**
**Send small auxiliary data to be received by app running on N64**
#### `arg0` (data)
| bits | description |
| -------- | ----------- |
| `[31:0]` | Data |
_This command does not send response data._
This command puts 32 bits of data to the AUX register accessible from the N64 side, and generates cart interrupt (if enabled).
---
### `D`: **DD_SET_BLOCK_READY**
**Notify flashcart about 64DD block readiness**
@ -482,6 +502,7 @@ Save data is sent via [**SAVE_WRITEBACK**](#s-save_writeback) asynchronous packe
| id | name | data | description |
| --- | --------------------------------------- | -------------------- | --------------------------------------------------------------------- |
| `X` | [**AUX_DATA**](#x-aux_data) | data | Data was written to the `AUX` register from the N64 side |
| `B` | [**BUTTON**](#b-button) | --- | Button on the back of the SC64 was pressed |
| `U` | [**DATA**](#u-data) | data | Data sent from the N64 |
| `G` | [**DATA_FLUSHED**](#g-data_flushed) | --- | Data from [`U` **USB_WRITE**](#u-usb_write) USB command was discarded |
@ -490,6 +511,20 @@ Save data is sent via [**SAVE_WRITEBACK**](#s-save_writeback) asynchronous packe
| `S` | [**SAVE_WRITEBACK**](#s-save_writeback) | save_contents | Flushed save data |
| `F` | [**UPDATE_STATUS**](#f-update_status) | progress | Firmware update progress |
---
### `X`: **AUX_DATA**
**Data was written to the `AUX` register from the N64 side**
This packet is sent when N64 writes to the `AUX` register in the SC64 register block.
#### `data` (data)
| offset | type | description |
| ------ | -------- | ----------- |
| `0` | uint32_t | Data |
---
### `B`: **BUTTON**

View File

@ -360,7 +360,8 @@ module mcu_top (
REG_DEBUG_0,
REG_DEBUG_1,
REG_CIC_0,
REG_CIC_1
REG_CIC_1,
REG_AUX
} reg_address_e;
logic bootloader_skip;
@ -372,6 +373,8 @@ module mcu_top (
logic cic_invalid_region;
logic aux_pending;
// Register read logic
@ -459,7 +462,9 @@ module mcu_top (
REG_CFG_CMD: begin
reg_rdata <= {
23'd0,
19'd0,
aux_pending,
3'd0,
n64_scb.cfg_pending,
n64_scb.cfg_cmd
};
@ -670,6 +675,10 @@ module mcu_top (
REG_CIC_1: begin
reg_rdata <= n64_scb.cic_checksum[31:0];
end
REG_AUX: begin
reg_rdata <= n64_scb.aux_rdata;
end
endcase
end
end
@ -700,7 +709,10 @@ module mcu_top (
n64_scb.cfg_done <= 1'b0;
n64_scb.cfg_error <= 1'b0;
n64_scb.cfg_irq <= 1'b0;
n64_scb.btn_irq <= 1'b0;
n64_scb.usb_irq <= 1'b0;
n64_scb.aux_irq <= 1'b0;
n64_scb.flashram_done <= 1'b0;
@ -731,6 +743,10 @@ module mcu_top (
cic_invalid_region <= 1'b1;
end
if (n64_scb.aux_pending) begin
aux_pending <= 1'b1;
end
if (reset) begin
mcu_int <= 1'b0;
sd_scb.clock_mode <= 2'd0;
@ -755,6 +771,7 @@ module mcu_top (
n64_scb.cic_region <= 1'b0;
n64_scb.cic_seed <= 8'h3F;
n64_scb.cic_checksum <= 48'hA536C0F1D859;
aux_pending <= 1'b0;
end else if (reg_write) begin
case (address)
REG_MEM_ADDRESS: begin
@ -771,6 +788,7 @@ module mcu_top (
end
REG_USB_SCR: begin
n64_scb.usb_irq <= reg_wdata[31];
usb_scb.write_buffer_flush <= reg_wdata[5];
usb_scb.reset_off_ack <= reg_wdata[4];
usb_scb.reset_on_ack <= reg_wdata[3];
@ -820,10 +838,13 @@ module mcu_top (
REG_CFG_CMD: begin
{
n64_scb.cfg_irq,
n64_scb.btn_irq,
n64_scb.cfg_error,
n64_scb.cfg_done
} <= reg_wdata[11:9];
if (reg_wdata[13]) begin
aux_pending <= 1'b0;
end
end
REG_FLASHRAM_SCR: begin
@ -947,6 +968,11 @@ module mcu_top (
REG_CIC_1: begin
n64_scb.cic_checksum[31:0] <= reg_wdata;
end
REG_AUX: begin
n64_scb.aux_irq <= 1'b1;
n64_scb.aux_wdata <= reg_wdata;
end
endcase
end
end

View File

@ -21,16 +21,29 @@ module n64_cfg (
REG_KEY_H,
REG_KEY_L,
REG_IRQ_H,
REG_IRQ_L
REG_IRQ_L,
REG_AUX_H,
REG_AUX_L
} e_reg;
logic cmd_error;
logic cmd_irq_request;
logic cmd_irq;
logic mcu_irq;
logic btn_irq;
logic usb_irq;
logic aux_irq;
logic usb_irq_enabled;
logic aux_irq_enabled;
always_ff @(posedge clk) begin
irq <= (cmd_irq || mcu_irq);
irq <= (
cmd_irq ||
btn_irq ||
(usb_irq_enabled && usb_irq) ||
(aux_irq_enabled && aux_irq)
);
end
always_comb begin
@ -40,9 +53,11 @@ module n64_cfg (
REG_STATUS: reg_bus.rdata = {
n64_scb.cfg_pending,
cmd_error,
mcu_irq,
btn_irq,
cmd_irq,
12'd0
usb_irq,
aux_irq,
10'd0
};
REG_COMMAND: reg_bus.rdata = {7'd0, cmd_irq_request, n64_scb.cfg_cmd};
REG_DATA_0_H: reg_bus.rdata = n64_scb.cfg_wdata[0][31:16];
@ -53,8 +68,17 @@ module n64_cfg (
REG_IDENTIFIER_L: reg_bus.rdata = n64_scb.cfg_identifier[15:0];
REG_KEY_H: reg_bus.rdata = 16'd0;
REG_KEY_L: reg_bus.rdata = 16'd0;
REG_IRQ_H: reg_bus.rdata = 16'd0;
REG_IRQ_H: reg_bus.rdata = {
8'd0,
1'b1,
1'b1,
usb_irq_enabled,
aux_irq_enabled,
4'd0
};
REG_IRQ_L: reg_bus.rdata = 16'd0;
REG_AUX_H: reg_bus.rdata = n64_scb.aux_wdata[31:16];
REG_AUX_L: reg_bus.rdata = n64_scb.aux_wdata[15:0];
endcase
end
end
@ -63,14 +87,26 @@ module n64_cfg (
logic lock_sequence_counter;
always_ff @(posedge clk) begin
n64_scb.aux_pending <= 1'b0;
if (n64_scb.cfg_pending && n64_scb.cfg_done) begin
n64_scb.cfg_pending <= 1'b0;
cmd_irq <= cmd_irq_request;
cmd_error <= n64_scb.cfg_error;
end
if (n64_scb.cfg_irq) begin
mcu_irq <= 1'b1;
if (n64_scb.cfg_unlock) begin
if (n64_scb.btn_irq) begin
btn_irq <= 1'b1;
end
if (n64_scb.usb_irq) begin
usb_irq <= 1'b1;
end
if (n64_scb.aux_irq) begin
aux_irq <= 1'b1;
end
end
if (unlock_flag) begin
@ -84,13 +120,21 @@ module n64_cfg (
cmd_error <= 1'b0;
cmd_irq_request <= 1'b0;
cmd_irq <= 1'b0;
mcu_irq <= 1'b0;
btn_irq <= 1'b0;
usb_irq <= 1'b0;
aux_irq <= 1'b0;
usb_irq_enabled <= 1'b0;
aux_irq_enabled <= 1'b0;
lock_sequence_counter <= 1'd0;
end else if (n64_scb.n64_reset || n64_scb.n64_nmi) begin
n64_scb.cfg_unlock <= 1'b0;
cmd_irq_request <= 1'b0;
cmd_irq <= 1'b0;
mcu_irq <= 1'b0;
btn_irq <= 1'b0;
usb_irq <= 1'b0;
aux_irq <= 1'b0;
usb_irq_enabled <= 1'b0;
aux_irq_enabled <= 1'b0;
lock_sequence_counter <= 1'd0;
end else if (n64_scb.cfg_unlock) begin
if (reg_bus.write && reg_bus.address[16] && (reg_bus.address[15:5] == 11'd0)) begin
@ -129,7 +173,7 @@ module n64_cfg (
end
REG_IDENTIFIER_H: begin
mcu_irq <= 1'b0;
btn_irq <= 1'b0;
end
REG_KEY_H, REG_KEY_L: begin
@ -142,14 +186,44 @@ module n64_cfg (
n64_scb.cfg_unlock <= 1'b0;
cmd_irq_request <= 1'b0;
cmd_irq <= 1'b0;
mcu_irq <= 1'b0;
btn_irq <= 1'b0;
usb_irq <= 1'b0;
aux_irq <= 1'b0;
usb_irq_enabled <= 1'b0;
aux_irq_enabled <= 1'b0;
end
end
end
REG_IRQ_H: begin
mcu_irq <= (reg_bus.wdata[15] ? 1'b0 : mcu_irq);
btn_irq <= (reg_bus.wdata[15] ? 1'b0 : btn_irq);
cmd_irq <= (reg_bus.wdata[14] ? 1'b0 : cmd_irq);
usb_irq <= (reg_bus.wdata[13] ? 1'b0 : usb_irq);
aux_irq <= (reg_bus.wdata[12] ? 1'b0 : aux_irq);
end
REG_IRQ_L: begin
if (reg_bus.wdata[11]) begin
usb_irq_enabled <= 1'b0;
end
if (reg_bus.wdata[10]) begin
usb_irq_enabled <= 1'b1;
end
if (reg_bus.wdata[9]) begin
aux_irq_enabled <= 1'b0;
end
if (reg_bus.wdata[8]) begin
aux_irq_enabled <= 1'b1;
end
end
REG_AUX_H: begin
n64_scb.aux_rdata[31:16] <= reg_bus.wdata;
end
REG_AUX_L: begin
n64_scb.aux_pending <= 1'b1;
n64_scb.aux_rdata[15:0] <= reg_bus.wdata;
end
endcase
end

View File

@ -47,12 +47,19 @@ interface n64_scb ();
logic cfg_pending;
logic cfg_done;
logic cfg_error;
logic cfg_irq;
logic [7:0] cfg_cmd;
logic [31:0] cfg_rdata [0:1];
logic [31:0] cfg_wdata [0:1];
logic [31:0] cfg_identifier;
logic btn_irq;
logic usb_irq;
logic aux_irq;
logic aux_pending;
logic [31:0] aux_rdata;
logic [31:0] aux_wdata;
logic [15:0] save_count;
logic cic_invalid_region;
@ -98,12 +105,19 @@ interface n64_scb ();
input cfg_pending,
output cfg_done,
output cfg_error,
output cfg_irq,
input cfg_cmd,
input cfg_rdata,
output cfg_wdata,
output cfg_identifier,
output btn_irq,
output usb_irq,
output aux_irq,
input aux_pending,
input aux_rdata,
output aux_wdata,
input save_count,
input cic_invalid_region,
@ -206,11 +220,18 @@ interface n64_scb ();
output cfg_pending,
input cfg_done,
input cfg_error,
input cfg_irq,
output cfg_cmd,
output cfg_rdata,
input cfg_wdata,
input cfg_identifier
input cfg_identifier,
input btn_irq,
input usb_irq,
input aux_irq,
output aux_pending,
output aux_rdata,
input aux_wdata
);
modport save_counter (

View File

@ -9,6 +9,7 @@ typedef struct {
io32_t IDENTIFIER;
io32_t KEY;
io32_t IRQ;
io32_t AUX;
} sc64_regs_t;
#define SC64_REGS_BASE (0x1FFF0000UL)
@ -16,8 +17,10 @@ typedef struct {
#define SC64_SR_CMD_IRQ_REQUEST (1 << 8)
#define SC64_SR_AUX_IRQ_PENDING (1 << 26)
#define SC64_SR_USB_IRQ_PENDING (1 << 27)
#define SC64_SR_CMD_IRQ_PENDING (1 << 28)
#define SC64_SR_MCU_IRQ_PENDING (1 << 29)
#define SC64_SR_BTN_IRQ_PENDING (1 << 29)
#define SC64_SR_CMD_ERROR (1 << 30)
#define SC64_SR_CPU_BUSY (1 << 31)
@ -28,8 +31,20 @@ typedef struct {
#define SC64_KEY_UNLOCK_2 (0x4F434B5FUL)
#define SC64_KEY_LOCK (0xFFFFFFFFUL)
#define SC64_IRQ_AUX_ENABLE (1 << 8)
#define SC64_IRQ_AUX_DISABLE (1 << 9)
#define SC64_IRQ_USB_ENABLE (1 << 10)
#define SC64_IRQ_USB_DISABLE (1 << 11)
#define SC64_IRQ_AUX_MASK (1 << 20)
#define SC64_IRQ_USB_MASK (1 << 21)
#define SC64_IRQ_CMD_MASK (1 << 22)
#define SC64_IRQ_BTN_MASK (1 << 23)
#define SC64_IRQ_AUX_CLEAR (1 << 28)
#define SC64_IRQ_USB_CLEAR (1 << 29)
#define SC64_IRQ_CMD_CLEAR (1 << 30)
#define SC64_IRQ_MCU_CLEAR (1 << 31)
#define SC64_IRQ_BTN_CLEAR (1 << 31)
typedef enum {
@ -109,18 +124,26 @@ static sc64_error_t sc64_execute_cmd (sc64_cmd_t *cmd) {
return SC64_OK;
}
static void sc64_mcu_irq_callback (void) {
error_display("[Unexpected] SC64 MCU interrupt received");
static void sc64_btn_irq_callback (void) {
error_display("[Unexpected] SC64 button pressed interrupt received");
}
static void sc64_cmd_irq_callback (void) {
if (wait_cmd_irq) {
wait_cmd_irq = false;
} else {
error_display("[Unexpected] SC64 CMD interrupt received");
error_display("[Unexpected] SC64 command finish interrupt received");
}
}
static void sc64_usb_irq_callback (void) {
error_display("[Unexpected] SC64 USB not empty interrupt received");
}
static void sc64_aux_irq_callback (void) {
error_display("[Unexpected] SC64 AUX not empty interrupt received");
}
const char *sc64_error_description (sc64_error_t error) {
if (error == SC64_OK) {
@ -205,33 +228,59 @@ void sc64_cmd_irq_enable (bool enable) {
use_cmd_irq = enable;
}
void sc64_usb_irq_enable (bool enable) {
pi_io_write(&SC64_REGS->IRQ, enable ? SC64_IRQ_USB_ENABLE : SC64_IRQ_USB_DISABLE);
}
void sc64_aux_irq_enable (bool enable) {
pi_io_write(&SC64_REGS->IRQ, enable ? SC64_IRQ_AUX_ENABLE : SC64_IRQ_AUX_DISABLE);
}
sc64_irq_t sc64_irq_pending (void) {
uint32_t sr = pi_io_read(&SC64_REGS->SR_CMD);
sc64_irq_t irq = SC64_IRQ_NONE;
if (sr & SC64_SR_MCU_IRQ_PENDING) {
irq |= SC64_IRQ_MCU;
if (sr & SC64_SR_BTN_IRQ_PENDING) {
irq |= SC64_IRQ_BTN;
}
if (sr & SC64_SR_CMD_IRQ_PENDING) {
irq |= SC64_IRQ_CMD;
}
if (sr & SC64_SR_USB_IRQ_PENDING) {
irq |= SC64_IRQ_USB;
}
if (sr & SC64_SR_AUX_IRQ_PENDING) {
irq |= SC64_IRQ_AUX;
}
return irq;
}
void sc64_irq_callback (sc64_irq_t irq) {
uint32_t clear = 0;
if (irq & SC64_IRQ_MCU) {
clear |= SC64_IRQ_MCU_CLEAR;
if (irq & SC64_IRQ_BTN) {
clear |= SC64_IRQ_BTN_CLEAR;
}
if (irq & SC64_IRQ_CMD) {
clear |= SC64_IRQ_CMD_CLEAR;
}
if (irq & SC64_IRQ_USB) {
clear |= SC64_IRQ_USB_CLEAR;
}
if (irq & SC64_IRQ_AUX) {
clear |= SC64_IRQ_AUX_CLEAR;
}
pi_io_write(&SC64_REGS->IRQ, clear);
if (irq & SC64_IRQ_MCU) {
sc64_mcu_irq_callback();
if (irq & SC64_IRQ_BTN) {
sc64_btn_irq_callback();
}
if (irq & SC64_IRQ_CMD) {
sc64_cmd_irq_callback();
}
if (irq & SC64_IRQ_USB) {
sc64_usb_irq_callback();
}
if (irq & SC64_IRQ_AUX) {
sc64_aux_irq_callback();
}
while (pi_busy());
}

View File

@ -166,8 +166,10 @@ typedef enum {
typedef enum {
SC64_IRQ_NONE = 0,
SC64_IRQ_MCU = (1 << 0),
SC64_IRQ_BTN = (1 << 0),
SC64_IRQ_CMD = (1 << 1),
SC64_IRQ_USB = (1 << 2),
SC64_IRQ_AUX = (1 << 3),
} sc64_irq_t;
@ -189,6 +191,8 @@ void sc64_lock (void);
bool sc64_check_presence (void);
void sc64_cmd_irq_enable (bool enable);
void sc64_usb_irq_enable (bool enable);
void sc64_aux_irq_enable (bool enable);
sc64_irq_t sc64_irq_pending (void);
void sc64_irq_callback (sc64_irq_t irq);

View File

@ -566,6 +566,8 @@ void test_execute (void) {
pi_io_config(0x0F, 0x05, 0x0C, 0x02);
sc64_cmd_irq_enable(true);
sc64_usb_irq_enable(true);
sc64_aux_irq_enable(true);
if ((error = sc64_set_config(CFG_ID_ROM_WRITE_ENABLE, true)) != SC64_OK) {
error_display("Command CONFIG_SET [ROM_WRITE_ENABLE] failed\n (%08X) - %s", error, sc64_error_description(error));

View File

@ -75,7 +75,7 @@ void button_process (void) {
if (p.trigger) {
switch (p.mode) {
case BUTTON_MODE_N64_IRQ:
fpga_reg_set(REG_CFG_CMD, CFG_CMD_IRQ);
fpga_reg_set(REG_CFG_CMD, CFG_CMD_BTN_IRQ);
p.trigger = false;
break;

View File

@ -141,9 +141,19 @@ static struct process p;
static bool cfg_cmd_check (void) {
if (!p.cmd_queued) {
uint32_t reg = fpga_reg_get(REG_CFG_CMD);
uint32_t reg = fpga_reg_get(REG_CFG_CMD);
if (reg & CFG_CMD_AUX_PENDING) {
usb_tx_info_t packet_info;
usb_create_packet(&packet_info, PACKET_CMD_AUX_DATA);
packet_info.data_length = 4;
packet_info.data[0] = fpga_reg_get(REG_AUX);
if (usb_enqueue_packet(&packet_info)) {
fpga_reg_set(REG_CFG_CMD, CFG_CMD_AUX_DONE);
}
}
if (!p.cmd_queued) {
if (!(reg & CFG_CMD_PENDING)) {
return true;
}

View File

@ -57,6 +57,7 @@ typedef enum {
REG_DEBUG_1,
REG_CIC_0,
REG_CIC_1,
REG_AUX,
} fpga_reg_t;
@ -89,6 +90,7 @@ typedef enum {
#define USB_SCR_RESET_STATE (1 << 28)
#define USB_SCR_PWRSAV (1 << 29)
#define USB_SCR_FIFO_FLUSH_BUSY (1 << 30)
#define USB_SCR_IRQ (1 << 31)
#define DMA_SCR_START (1 << 0)
#define DMA_SCR_STOP (1 << 1)
@ -115,7 +117,9 @@ typedef enum {
#define CFG_CMD_PENDING (1 << 8)
#define CFG_CMD_DONE (1 << 9)
#define CFG_CMD_ERROR (1 << 10)
#define CFG_CMD_IRQ (1 << 11)
#define CFG_CMD_BTN_IRQ (1 << 11)
#define CFG_CMD_AUX_PENDING (1 << 12)
#define CFG_CMD_AUX_DONE (1 << 13)
#define FLASHRAM_SCR_DONE (1 << 0)
#define FLASHRAM_SCR_PENDING (1 << 1)

View File

@ -231,9 +231,6 @@ static void usb_rx_process (void) {
p.response_info.data_length = 0;
p.response_info.dma_length = 0;
p.response_info.done_callback = NULL;
if (p.rx_cmd == 'U') {
timer_countdown_start(TIMER_ID_USB, DEBUG_WRITE_TIMEOUT_MS);
}
}
}
@ -243,6 +240,10 @@ static void usb_rx_process (void) {
if (p.rx_counter == 2) {
p.rx_counter = 0;
p.rx_state = RX_STATE_DATA;
if ((p.rx_cmd == 'U') && (p.rx_args[0] > 0)) {
fpga_reg_set(REG_USB_SCR, USB_SCR_IRQ);
timer_countdown_start(TIMER_ID_USB, DEBUG_WRITE_TIMEOUT_MS);
}
break;
}
}
@ -377,6 +378,12 @@ static void usb_rx_process (void) {
}
break;
case 'X':
fpga_reg_set(REG_AUX, p.rx_args[0]);
p.rx_state = RX_STATE_IDLE;
p.response_pending = true;
break;
case 'D':
dd_set_block_ready(p.rx_args[0] == 0);
p.rx_state = RX_STATE_IDLE;

View File

@ -7,6 +7,7 @@
typedef enum packet_cmd {
PACKET_CMD_AUX_DATA = 'X',
PACKET_CMD_BUTTON_TRIGGER = 'B',
PACKET_CMD_DATA_FLUSHED = 'G',
PACKET_CMD_DEBUG_OUTPUT = 'U',

View File

@ -87,6 +87,10 @@ struct UploadArgs {
/// Path to the ROM file
rom: PathBuf,
/// Attempt to reboot the console (requires specific support in the running game)
#[arg(short, long)]
reboot: bool,
/// Path to the save file
#[arg(short, long)]
save: Option<PathBuf>,
@ -363,8 +367,29 @@ fn handle_list_command() -> Result<(), sc64::Error> {
}
fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<(), sc64::Error> {
const AUX_TOKEN_UPLOAD_START: u32 = 0xFF000001;
const AUX_TOKEN_REBOOT: u32 = 0xFF000002;
let mut sc64 = init_sc64(connection, true)?;
if args.reboot {
match sc64.aux_send_and_receive(
AUX_TOKEN_UPLOAD_START,
std::time::Duration::from_millis(500),
)? {
Some(data) => println!(
"{}",
format!("N64 reboot prepare successful (0x{data:08X})")
.bright_green()
.bold()
),
None => println!(
"{}",
"N64 reboot prepare unsuccessful".bright_yellow().bold()
),
}
}
sc64.reset_state()?;
let (mut rom_file, rom_name, rom_length) = open_file(&args.rom)?;
@ -410,6 +435,10 @@ fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<()
sc64.calculate_cic_parameters(args.cic_seed)?;
if args.reboot {
sc64.aux_send(AUX_TOKEN_REBOOT)?;
}
Ok(())
}

View File

@ -230,6 +230,11 @@ impl SC64 {
Ok(())
}
fn command_aux_write(&mut self, data: u32) -> Result<(), Error> {
self.link.execute_command(b'X', [data, 0], &[])?;
Ok(())
}
fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> {
self.link.execute_command(b'D', [error as u32, 0], &[])?;
Ok(())
@ -566,6 +571,33 @@ impl SC64 {
self.command_usb_write(debug_packet.datatype, &debug_packet.data)
}
pub fn aux_send(&mut self, data: u32) -> Result<(), Error> {
self.command_aux_write(data)
}
pub fn aux_send_and_receive(
&mut self,
data: u32,
timeout: std::time::Duration,
) -> Result<Option<u32>, Error> {
self.aux_send(data)?;
let reply_timeout = std::time::Instant::now();
loop {
match self.receive_data_packet()? {
Some(packet) => match packet {
DataPacket::AuxData(data) => {
return Ok(Some(data));
}
_ => {}
},
None => {}
}
if reply_timeout.elapsed() > timeout {
return Ok(None);
}
}
}
pub fn check_device(&mut self) -> Result<(), Error> {
let identifier = self.command_identifier_get().map_err(|e| {
Error::new(format!("Couldn't get SC64 device identifier: {e}").as_str())

View File

@ -614,6 +614,7 @@ impl From<Setting> for [u32; 2] {
}
pub enum DataPacket {
AuxData(u32),
Button,
DataFlushed,
DebugData(DebugPacket),
@ -627,6 +628,7 @@ impl TryFrom<AsynchronousPacket> for DataPacket {
type Error = Error;
fn try_from(value: AsynchronousPacket) -> Result<Self, Self::Error> {
Ok(match value.id {
b'X' => Self::AuxData(u32::from_be_bytes(value.data[0..4].try_into().unwrap())),
b'B' => Self::Button,
b'G' => Self::DataFlushed,
b'U' => Self::DebugData(value.data.try_into()?),
@ -1020,7 +1022,7 @@ impl Display for DiagnosticData {
pub enum SpeedTestDirection {
Read,
Write
Write,
}
pub enum MemoryTestPattern {