From af171135a3690c556bbfe164afeb9b789ebe8c55 Mon Sep 17 00:00:00 2001 From: Doug Brown Date: Sat, 31 Jul 2021 17:06:58 -0700 Subject: [PATCH] Initial implementation of simulated flash chips There is still a lot of work to do in order to make it pretty, but this is a great start! Chip ID function works. --- .gitignore | 1 + hal/pc/board.c | 23 ++ hal/pc/flash_4mbit.c | 496 ++++++++++++++++++++++++++++++++++++++++ hal/pc/flash_4mbit.h | 79 +++++++ hal/pc/gpio.c | 137 +++++++++-- hal/pc/gpio_hw.h | 4 +- hal/pc/gpio_sim.c | 100 ++++++++ hal/pc/gpio_sim.h | 62 +++++ hal/pc/parallel_bus.c | 66 ++++-- hal/pc/pc_sources.cmake | 2 + 10 files changed, 940 insertions(+), 30 deletions(-) create mode 100644 hal/pc/flash_4mbit.c create mode 100644 hal/pc/flash_4mbit.h create mode 100644 hal/pc/gpio_sim.c create mode 100644 hal/pc/gpio_sim.h diff --git a/.gitignore b/.gitignore index b3fced3..0690c83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /Debug /Release .settings/org.eclipse.cdt.core.prefs +CMakeLists.txt.user diff --git a/hal/pc/board.c b/hal/pc/board.c index b66a7ca..cd08e29 100644 --- a/hal/pc/board.c +++ b/hal/pc/board.c @@ -23,6 +23,7 @@ */ #include "board_hw.h" +#include "flash_4mbit.h" /** Initializes any board hardware-specific stuff * @@ -30,6 +31,28 @@ void Board_Init(void) { // Nothing to do on PC + + // TODO: Figure out a way to make this dynamically configurable. But for now... + GPIOPin cs = {GPIOMISC, 4}; + GPIOPin oe = {GPIOMISC, 5}; + GPIOPin we = {GPIOMISC, 6}; + static Flash4MBit ics[4]; + for (uint8_t i = 0; i < 4; i++) + { + Flash4MBit_Init(&ics[i], Flash_SST39SF040); + GPIOSim_AddDevice(&ics[i].base); + Flash4MBit_SetControlPins(&ics[i], cs, oe, we); + for (uint8_t j = 0; j < FLASH_4MBIT_DATA_PINS; j++) + { + GPIOPin dataPin = {GPIODATA, i * 8 + j}; + Flash4MBit_SetDataPin(&ics[i], j, dataPin); + } + for (uint8_t j = 0; j < FLASH_4MBIT_ADDR_PINS; j++) + { + GPIOPin addrPin = {GPIOADDR, j}; + Flash4MBit_SetAddressPin(&ics[i], j, addrPin); + } + } } /** Determines if a brownout was detected at startup diff --git a/hal/pc/flash_4mbit.c b/hal/pc/flash_4mbit.c new file mode 100644 index 0000000..8f3af3f --- /dev/null +++ b/hal/pc/flash_4mbit.c @@ -0,0 +1,496 @@ +/* + * flash_4mbit.c + * + * Created on: Jul 27, 2021 + * Author: Doug + * + * Copyright (C) 2011-2021 Doug Brown + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "flash_4mbit.h" +#include +#include + +// TODO: Can we use a timer after an erase operation begins? +// I'd rather not base the emulation on the number of confirm read cycles +// we get... + +static void UpdateGPIOPin(GPIOSimDevice *device, GPIOPin pin, bool high); +static GPIOSimValue ReadGPIOPin(GPIOSimDevice *device, GPIOPin pin); +static void HandleWriteCycle(Flash4MBit *f, uint32_t address, uint8_t data); +static void HandleReadCycle(Flash4MBit *f); +static int8_t DataPinIndex(Flash4MBit *f, GPIOPin pin); +static int8_t AddressPinIndex(Flash4MBit *f, GPIOPin pin); +static bool IsCSPin(Flash4MBit *f, GPIOPin pin); +static bool IsOEPin(Flash4MBit *f, GPIOPin pin); +static bool IsWEPin(Flash4MBit *f, GPIOPin pin); +static uint32_t UnlockAddressMask(Flash4MBit *f); +static uint8_t ManufacturerID(Flash4MBit *f); +static uint8_t DeviceID(Flash4MBit *f); +static void EraseChip(Flash4MBit *f); +static void EraseSector(Flash4MBit *f, uint32_t address); + +static const GPIOSimDeviceFunctions functions = { + .drivePin = UpdateGPIOPin, + .readPin = ReadGPIOPin, +}; + +enum FlashState +{ + FlashReading, + FlashUnlocking, + FlashWaitingForCommand, + FlashSoftwareID, + FlashProgrammingByte, + FlashEraseUnlocking1, + FlashEraseUnlocking2, + FlashEraseWaitingForCommand, + FlashReadingWriteOrEraseStatus, +}; + +/** + * @brief Initializes emulation for a single 4-megabit flash chip + * @param f The flash chip object + * @param t The type of flash chip being emulated + */ +void Flash4MBit_Init(Flash4MBit *f, Flash4MBitDeviceType t) +{ + f->base.functions = &functions; + f->type = t; + f->dataInput = 0; + f->address = 0; + f->latchedWriteAddress = 0; + f->csAsserted = false; + f->oeAsserted = false; + f->weAsserted = false; + f->softwareIDActivated = false; + f->state = FlashReading; + f->content = malloc(512*1024); + + // TODO: Dealloc content +} + +void Flash4MBit_SetControlPins(Flash4MBit *f, GPIOPin cs, GPIOPin oe, GPIOPin we) +{ + f->csPin = cs; + f->oePin = oe; + f->wePin = we; +} + +void Flash4MBit_SetDataPin(Flash4MBit *f, uint8_t index, GPIOPin pin) +{ + if (index < FLASH_4MBIT_DATA_PINS) + { + f->dataPins[index] = pin; + } +} + +void Flash4MBit_SetAddressPin(Flash4MBit *f, uint8_t index, GPIOPin pin) +{ + if (index < FLASH_4MBIT_ADDR_PINS) + { + f->addressPins[index] = pin; + } +} + +static void UpdateGPIOPin(GPIOSimDevice *device, GPIOPin pin, bool high) +{ + Flash4MBit *f = (Flash4MBit *)device; + int8_t pinIndex; + + if (IsCSPin(f, pin)) + { + if (f->csAsserted != !high) + { + f->csAsserted = !high; + + // If WE is asserted, latch stuff... + if (f->weAsserted) + { + // Latch the address on the falling edge of CS + if (f->csAsserted) + { + f->latchedWriteAddress = f->address; + } + // Latch the data (and update state) on the rising edge of CS + else + { + HandleWriteCycle(f, f->latchedWriteAddress, f->dataInput); + } + } + else if (f->oeAsserted) + { + if (!f->csAsserted) + { + // Handle a read cycle when CS is deasserted while OE is asserted. + // The data isn't really important; it's more of a state machine update + HandleReadCycle(f); + } + } + } + } + else if (IsOEPin(f, pin)) + { + if (f->oeAsserted != !high) + { + f->oeAsserted = !high; + + // If CS is asserted, latch stuff... + if (f->csAsserted) + { + if (!f->oeAsserted) + { + // Handle a read cycle when OE is deasserted while CS is asserted. + // The data isn't really important; it's more of a state machine update + HandleReadCycle(f); + } + } + } + } + else if (IsWEPin(f, pin)) + { + if (f->weAsserted != !high) + { + f->weAsserted = !high; + + // If CS is asserted, latch stuff... + if (f->csAsserted) + { + // Latch the address on the falling edge of WE + if (f->weAsserted) + { + f->latchedWriteAddress = f->address; + } + // Latch the data (and update state) on the rising edge of WE + else + { + HandleWriteCycle(f, f->latchedWriteAddress, f->dataInput); + } + } + } + } + else if ((pinIndex = DataPinIndex(f, pin)) >= 0) + { + // Update the current data input state + if (high) + { + f->dataInput |= (1UL << pinIndex); + } + else + { + f->dataInput &= ~(1UL << pinIndex); + } + } + else if ((pinIndex = AddressPinIndex(f, pin)) >= 0) + { + // Update the current address + if (high) + { + f->address |= (1UL << pinIndex); + } + else + { + f->address &= ~(1UL << pinIndex); + } + } +} + +static GPIOSimValue ReadGPIOPin(GPIOSimDevice *device, GPIOPin pin) +{ + Flash4MBit *f = (Flash4MBit *)device; + + // We only read when allowed + if (f->csAsserted && f->oeAsserted) + { + // Figure out if this is a data pin, that's the only pin we ever drive + int8_t dataPinIndex = DataPinIndex(f, pin); + if (dataPinIndex >= 0) + { + uint8_t dataToRead = 0; + + if (f->state == FlashSoftwareID) + { + // This is kind of janky, chips might have other internal registers to read. The different chip + // types are differently picky about what bits need to be zeroed for this operation. For the purposes + // of this emulation, just look at the bottom bit to decide manufacturer or device. + if (f->address & 1) + { + dataToRead = DeviceID(f); + } + else + { + dataToRead = ManufacturerID(f); + } + } + // TODO: programming toggle bit state? + else // For example, state is FlashReading, or we're interrupting an unlock sequence + { + // Grab the content from the current address + dataToRead = f->content[f->address]; + } + + // Drive high or low based on the data we're reading + if (dataToRead & (1 << dataPinIndex)) + { + return GPIOSimDrivingHigh; + } + else + { + return GPIOSimDrivingLow; + } + } + } + + return GPIOSimNotDriving; +} + +static void HandleWriteCycle(Flash4MBit *f, uint32_t address, uint8_t data) +{ + uint32_t unlockMask = UnlockAddressMask(f); + switch (f->state) + { + case FlashReadingWriteOrEraseStatus: + // Don't allow any changes to state while a write/erase is active + break; + case FlashReading: + case FlashSoftwareID: // TODO: Does this state even need to exist? Or is the softwareIDActivated bool enough? + default: + if (((address & unlockMask) == (0x55555555UL & unlockMask)) && (data == 0xAA)) + { + f->state = FlashUnlocking; + } + else if (data == 0xF0) + { + // Force a reset back to software ID mode + f->softwareIDActivated = false; + f->state = FlashReading; + } + break; + case FlashUnlocking: + if (((address & unlockMask) == (0xAAAAAAAAUL & unlockMask)) && (data == 0x55)) + { + f->state = FlashWaitingForCommand; + } + else + { + // Invalid unlock sequence + f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading; + } + break; + case FlashWaitingForCommand: + if ((address & unlockMask) == (0x55555555UL & unlockMask)) + { + switch (data) + { + case 0xA0: + f->state = FlashProgrammingByte; + break; + case 0x80: + f->state = FlashEraseUnlocking1; + break; + case 0x90: + f->softwareIDActivated = true; + f->state = FlashSoftwareID; + break; + case 0xF0: + f->softwareIDActivated = false; + f->state = FlashReading; + break; + default: + // Invalid command, go back to the read state we should be in + f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading; + break; + } + } + else + { + // Invalid unlock sequence + f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading; + } + break; + case FlashProgrammingByte: + // Write the data, then begin reading back the status + // Programming is only capable of turning 1 bits into zero bits. + // In other words, programming takes the current byte stored in the flash + // and ANDs it with the value you're trying to program to that byte. + // The resulting value is what gets programmed to the chip. + f->content[address & 0x7FFFF] &= data; + /// TODO: Set up timer for erase? + f->state = FlashReadingWriteOrEraseStatus; + break; + case FlashEraseUnlocking1: + if (((address & unlockMask) == (0x55555555UL & unlockMask)) && (data == 0xAA)) + { + f->state = FlashEraseUnlocking2; + } + else + { + f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading; + } + break; + case FlashEraseUnlocking2: + if (((address & unlockMask) == (0xAAAAAAAAUL & unlockMask)) && (data == 0x55)) + { + f->state = FlashEraseWaitingForCommand; + } + else + { + // Invalid unlock sequence + f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading; + } + break; + case FlashEraseWaitingForCommand: + if (((address & unlockMask) == (0x55555555UL & unlockMask)) && data == 0x10) + { + // Erase the entire chip + EraseChip(f); + /// TODO: Set up timer for erase? + f->state = FlashReadingWriteOrEraseStatus; + } + else if (data == 0x30) + { + EraseSector(f, address); + /// TODO: Set up timer for erase? + f->state = FlashReadingWriteOrEraseStatus; + } + else + { + // Invalid command/address supplied during erase + f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading; + } + break; + } +} + +static void HandleReadCycle(Flash4MBit *f) +{ + // We don't care about the address here because most of the output logic is handled in the ReadGPIOPin handler. + // This just makes sure the state machine is up to date after a read cycle. +} + +static int8_t DataPinIndex(Flash4MBit *f, GPIOPin pin) +{ + for (int i = 0; i < FLASH_4MBIT_DATA_PINS; i++) + { + if (f->dataPins[i].port == pin.port && + f->dataPins[i].pin == pin.pin) + { + return i; + } + } + return -1; +} + +static int8_t AddressPinIndex(Flash4MBit *f, GPIOPin pin) +{ + for (int i = 0; i < FLASH_4MBIT_ADDR_PINS; i++) + { + if (f->addressPins[i].port == pin.port && + f->addressPins[i].pin == pin.pin) + { + return i; + } + } + return -1; +} + +static bool IsCSPin(Flash4MBit *f, GPIOPin pin) +{ + return f->csPin.port == pin.port && f->csPin.pin == pin.pin; +} + +static bool IsOEPin(Flash4MBit *f, GPIOPin pin) +{ + return f->oePin.port == pin.port && f->oePin.pin == pin.pin; +} + +static bool IsWEPin(Flash4MBit *f, GPIOPin pin) +{ + return f->wePin.port == pin.port && f->wePin.pin == pin.pin; +} + +static uint32_t UnlockAddressMask(Flash4MBit *f) +{ + switch (f->type) + { + case Flash_SST39SF040: + // SST39SF040 only cares about A0 through A14 (low 15 bits) + return 0x7FFFUL; + case Flash_AM29F040B: + // AM329F040B only cares about A0 through A10 (low 11 bits) + return 0x7FFUL; + default: + // Default behavior is we care about all 19 address bits during unlock operations + return 0x7FFFFUL; + } +} + +static uint8_t ManufacturerID(Flash4MBit *f) +{ + switch (f->type) + { + case Flash_SST39SF040: + return 0xBF; + case Flash_AM29F040B: + return 0x01; + default: + return 0; + } +} + +static uint8_t DeviceID(Flash4MBit *f) +{ + switch (f->type) + { + case Flash_SST39SF040: + return 0xB7; + case Flash_AM29F040B: + return 0xA4; + default: + return 0; + } +} +static void EraseChip(Flash4MBit *f) +{ + memset(f->content, 0xFF, 512*1024); +} + +static void EraseSector(Flash4MBit *f, uint32_t address) +{ + uint32_t sectorSize; + + switch (f->type) + { + case Flash_SST39SF040: + default: + // 4 KB sectors + sectorSize = 4 * 1024; + break; + case Flash_AM29F040B: + // 64 KB sectors + sectorSize = 64 * 1024; + break; + } + + // Mask out the low bits of the address so we have the sector start address. + // We're guaranteed that sectorSize is a power of 2, so subtracting 1 will + // give us a mask we can use to turn off address bits to get the start addr. + address &= ~(sectorSize - 1); + // Clear those bytes + memset(f->content + address, 0xFF, sectorSize); +} diff --git a/hal/pc/flash_4mbit.h b/hal/pc/flash_4mbit.h new file mode 100644 index 0000000..d11e00e --- /dev/null +++ b/hal/pc/flash_4mbit.h @@ -0,0 +1,79 @@ +/* + * flash_4mbit.h + * + * Created on: Jul 27, 2021 + * Author: Doug + * + * Copyright (C) 2011-2021 Doug Brown + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef FLASH_4MBIT_H +#define FLASH_4MBIT_H + +#include "gpio_sim.h" + +#define FLASH_4MBIT_DATA_PINS 8 +#define FLASH_4MBIT_ADDR_PINS 19 + +/// Enum representing the type of 4-megabit flash chip being emulated +typedef enum Flash4MBitDeviceType +{ + /// SST/Microchip SST39SF040 4-megabit flash chip + Flash_SST39SF040, + /// AMD/Infineon AM29F040B 4-megabit flash chips + Flash_AM29F040B, +} Flash4MBitDeviceType; + +/// Implementation of GPIOSimDevice for a 4-megabit flash chip +typedef struct Flash4MBit +{ + /// Base class + GPIOSimDevice base; + /// The type of flash being emulated + Flash4MBitDeviceType type; + /// Current data input state + uint8_t dataInput; + /// Current address + uint32_t address; + /// The write address that has been latched + uint32_t latchedWriteAddress; + /// True if CS is asserted + bool csAsserted; + /// True if OE is asserted + bool oeAsserted; + /// True if WE is asserted + bool weAsserted; + /// True if we've activated software ID mode + bool softwareIDActivated; + /// All of our pins + GPIOPin csPin; + GPIOPin oePin; + GPIOPin wePin; + GPIOPin dataPins[FLASH_4MBIT_DATA_PINS]; + GPIOPin addressPins[FLASH_4MBIT_ADDR_PINS]; + /// The state -- private so we store as an 8-bit int rather than the enum + uint8_t state; + uint8_t *content; +} Flash4MBit; + +void Flash4MBit_Init(Flash4MBit *f, Flash4MBitDeviceType t); +void Flash4MBit_SetControlPins(Flash4MBit *f, GPIOPin cs, GPIOPin oe, GPIOPin we); +void Flash4MBit_SetDataPin(Flash4MBit *f, uint8_t index, GPIOPin pin); +void Flash4MBit_SetAddressPin(Flash4MBit *f, uint8_t index, GPIOPin pin); + +#endif // FLASH_4MBIT_H diff --git a/hal/pc/gpio.c b/hal/pc/gpio.c index c412f17..a0527e3 100644 --- a/hal/pc/gpio.c +++ b/hal/pc/gpio.c @@ -23,6 +23,12 @@ */ #include "../gpio.h" +#include "gpio_hw.h" +#include "gpio_sim.h" + +static uint32_t directionReg[NUM_SIM_GPIO_PORTS]; +static uint32_t pullupReg[NUM_SIM_GPIO_PORTS]; +static uint32_t outputReg[NUM_SIM_GPIO_PORTS]; /** Sets the direction of a GPIO pin. * @@ -31,8 +37,26 @@ */ void GPIO_SetDirection(GPIOPin pin, bool output) { - // TODO: Modify direction - (void)pin; (void)output; + if (pin.port < NUM_SIM_GPIO_PORTS) + { + // Figure out if any state is actually changing, and if so, modify it + bool alreadyOutput = directionReg[pin.port] & (1UL << pin.pin); + if (output != alreadyOutput) + { + if (output) + { + directionReg[pin.port] |= (1UL << pin.pin); + // We just became an output, so make sure any sim devices are updated with + // the new driven value + GPIOSim_WritePin(pin, outputReg[pin.port] & (1UL << pin.pin)); + } + else + { + directionReg[pin.port] &= ~(1UL << pin.pin); + // We just became an input, there's no state to update really + } + } + } } /** Sets whether an input GPIO pin is pulled up @@ -42,8 +66,19 @@ void GPIO_SetDirection(GPIOPin pin, bool output) */ void GPIO_SetPullup(GPIOPin pin, bool pullup) { - // TODO: Modify pullup - (void)pin; (void)pullup; + if (pin.port < NUM_SIM_GPIO_PORTS) + { + // Update the pullup register. No need to update any other state, + // it simply affects the readback simulation + if (pullup) + { + pullupReg[pin.port] |= (1UL << pin.pin); + } + else + { + pullupReg[pin.port] &= ~(1UL << pin.pin); + } + } } /** Turns a GPIO pin on (sets it high) @@ -52,8 +87,19 @@ void GPIO_SetPullup(GPIOPin pin, bool pullup) */ void GPIO_SetOn(GPIOPin pin) { - // TODO: Turn on - (void)pin; + if (pin.port < NUM_SIM_GPIO_PORTS) + { + // Determine if we're actually changing the current state, which would + // require us to update simulated devices + bool isOutput = directionReg[pin.port] & (1UL << pin.pin); + bool curOutputValue = outputReg[pin.port] & (1UL << pin.pin); + bool needsNotification = isOutput && !curOutputValue; + outputReg[pin.port] |= (1UL << pin.pin); + if (needsNotification) + { + GPIOSim_WritePin(pin, true); + } + } } /** Turns a GPIO pin off (sets it low) @@ -62,8 +108,19 @@ void GPIO_SetOn(GPIOPin pin) */ void GPIO_SetOff(GPIOPin pin) { - // TODO: Turn off - (void)pin; + if (pin.port < NUM_SIM_GPIO_PORTS) + { + // Determine if we're actually changing the current state, which would + // require us to update simulated devices + bool isOutput = directionReg[pin.port] & (1UL << pin.pin); + bool curOutputValue = outputReg[pin.port] & (1UL << pin.pin); + bool needsNotification = isOutput && curOutputValue; + outputReg[pin.port] &= ~(1UL << pin.pin); + if (needsNotification) + { + GPIOSim_WritePin(pin, false); + } + } } /** Toggles a GPIO pin @@ -72,8 +129,18 @@ void GPIO_SetOff(GPIOPin pin) */ void GPIO_Toggle(GPIOPin pin) { - // TODO: Toggle - (void)pin; + if (pin.port < NUM_SIM_GPIO_PORTS) + { + // Figure out whether we are turning it on or off, and forward on + if (outputReg[pin.port] & (1UL << pin.pin)) + { + GPIO_SetOff(pin); + } + else + { + GPIO_SetOn(pin); + } + } } /** Reads the input status of a GPIO pin @@ -83,7 +150,51 @@ void GPIO_Toggle(GPIOPin pin) */ bool GPIO_Read(GPIOPin pin) { - // TODO: Read - (void)pin; - return false; + if (pin.port < NUM_SIM_GPIO_PORTS) + { + // If we are currently configured as an output, just read back the output value. + // We'll pretend that's what our "simulated" hardware does. + if (directionReg[pin.port] & (1UL << pin.pin)) + { + return outputReg[pin.port] & (1UL << pin.pin); + } + else + { + // If we're configured as an input, read back the value from any simulators + GPIOSimValue readback = GPIOSim_ReadPin(pin); + switch (readback) + { + case GPIOSimNotDriving: + default: + if (pullupReg[pin.port] & (1UL << pin.pin)) + { + // If the pull-up is active and nothing is driving the pin, + // read back as high + return true; + } + else + { + // If the pull-up is not active and nothing is driving the pin, + // it's floating. For the purposes of our simulation, let's return low. + // We could return random values if we wanted... + // TODO: assertion with current GPIO state, stack trace? + return false; + } + case GPIOSimDrivingLow: + return false; + case GPIOSimDrivingHigh: + return true; + case GPIOSimDrivingConflict: + // If it's being driven both high and low, bad things will happen. + // For the purposes of our simulation, read back as high. + // TODO: assertion with current GPIO state, stack trace? + return true; + } + } + } + else + { + // Read values as low if an invalid port is passed in + return false; + } } diff --git a/hal/pc/gpio_hw.h b/hal/pc/gpio_hw.h index 031f3f9..9f57a9a 100644 --- a/hal/pc/gpio_hw.h +++ b/hal/pc/gpio_hw.h @@ -29,13 +29,15 @@ /// Used with the GPIOPin struct. enum { /// The PC simulated hardware is laid out in a special way so that pins that - /// belong together are grouped onto virtual "ports". All of the address pins + /// belong together are grouped onto virtual 32-bit "ports". All of the address pins /// comprise one port, all of the data pins comprise another port, and other /// miscellaneous pins like LEDs and parallel bus control pins comprise one /// additional port. GPIOMISC, GPIOADDR, GPIODATA, + /// We also have a define to know how many actual ports we have + NUM_SIM_GPIO_PORTS }; #endif /* HAL_PC_GPIO_HW_H_ */ diff --git a/hal/pc/gpio_sim.c b/hal/pc/gpio_sim.c new file mode 100644 index 0000000..0648078 --- /dev/null +++ b/hal/pc/gpio_sim.c @@ -0,0 +1,100 @@ +/* + * gpio_sim.c + * + * Created on: Jul 31, 2021 + * Author: Doug + * + * Copyright (C) 2011-2021 Doug Brown + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gpio_sim.h" +#include + +static GPIOSimDevice *gpioSims = NULL; + +void GPIOSim_AddDevice(GPIOSimDevice *device) +{ + // We keep a super simple linked list, ordering doesn't matter. + // For simplicity, prepend this device to the linked list. + device->next = gpioSims; + gpioSims = device; +} + +void GPIOSim_WritePin(GPIOPin pin, bool high) +{ + // Give each simulated device a chance to handle this event + GPIOSimDevice *device = gpioSims; + while (device) + { + if (device->functions && device->functions->drivePin) + { + device->functions->drivePin(device, pin, high); + } + device = device->next; + } +} + +GPIOSimValue GPIOSim_ReadPin(GPIOPin pin) +{ + GPIOSimValue retval = GPIOSimNotDriving; + + // Give each simulated device a chance to handle this read + GPIOSimDevice *device = gpioSims; + while (device) + { + if (device->functions && device->functions->readPin) + { + GPIOSimValue val = device->functions->readPin(device, pin); + switch (val) + { + case GPIOSimNotDriving: + default: + // Do nothing if we're not driving this pin + break; + case GPIOSimDrivingLow: + // If we haven't found anything driving it yet, we can be the driver + if (retval == GPIOSimNotDriving) + { + retval = GPIOSimDrivingLow; + } + // If we already found something driving it high, mark the error condition + else if (retval == GPIOSimDrivingHigh) + { + retval = GPIOSimDrivingConflict; + } + break; + case GPIOSimDrivingHigh: + // If we haven't found anything driving it yet, we can be the driver + if (retval == GPIOSimNotDriving) + { + retval = GPIOSimDrivingHigh; + } + // If we already found something driving it low, mark the error condition + else if (retval == GPIOSimDrivingLow) + { + retval = GPIOSimDrivingConflict; + } + break; + } + } + device = device->next; + } + + // All done, return retval + return retval; +} diff --git a/hal/pc/gpio_sim.h b/hal/pc/gpio_sim.h new file mode 100644 index 0000000..13661bd --- /dev/null +++ b/hal/pc/gpio_sim.h @@ -0,0 +1,62 @@ +/* + * gpio_sim.h + * + * Created on: Jul 31, 2021 + * Author: Doug + * + * Copyright (C) 2011-2021 Doug Brown + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GPIO_SIM_H +#define GPIO_SIM_H + +#include "../gpio.h" + +/// Enum describing the various ways a simulated GPIO device can be driving a GPIO pin +typedef enum GPIOSimValue +{ + /// The simulated GPIO is not driving this pin + GPIOSimNotDriving, + /// The simulated GPIO is driven low + GPIOSimDrivingLow, + /// The simulated GPIO is driven high + GPIOSimDrivingHigh, + /// It's being driven both high and low, uh oh. + /// Actual devices don't return this value, but it's used as a return value for GPIOSim_ReadPin + GPIOSimDrivingConflict +} GPIOSimValue; + +typedef struct GPIOSimDevice GPIOSimDevice; + +typedef struct GPIOSimDeviceFunctions +{ + void (*drivePin)(GPIOSimDevice *device, GPIOPin pin, bool high); + GPIOSimValue (*readPin)(GPIOSimDevice *device, GPIOPin pin); +} GPIOSimDeviceFunctions; + +struct GPIOSimDevice +{ + GPIOSimDeviceFunctions const *functions; + GPIOSimDevice *next; +}; + +void GPIOSim_AddDevice(GPIOSimDevice *device); +void GPIOSim_WritePin(GPIOPin pin, bool high); +GPIOSimValue GPIOSim_ReadPin(GPIOPin pin); + +#endif // GPIO_SIM_H diff --git a/hal/pc/parallel_bus.c b/hal/pc/parallel_bus.c index 43de8ab..6fa4ecd 100644 --- a/hal/pc/parallel_bus.c +++ b/hal/pc/parallel_bus.c @@ -76,8 +76,11 @@ void ParallelBus_Init(void) */ void ParallelBus_SetAddress(uint32_t address) { - // TODO: Fill out entire address in GPIOADDR - (void)address; + for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++) + { + GPIOPin addrPin = {GPIOADDR, i}; + GPIO_Set(addrPin, address & (1UL << i)); + } } /** Sets the output data on the 32-bit data bus @@ -86,8 +89,11 @@ void ParallelBus_SetAddress(uint32_t address) */ void ParallelBus_SetData(uint32_t data) { - // TODO: Fill out entire data in GPIODATA - (void)data; + for (int i = 0; i < 32; i++) + { + GPIOPin dataPin = {GPIODATA, i}; + GPIO_Set(dataPin, data & (1UL << i)); + } } /** Sets the output value of the CS pin @@ -126,8 +132,11 @@ void ParallelBus_SetWE(bool high) */ void ParallelBus_SetAddressDir(uint32_t outputs) { - // TODO: Set direction of GPIOADDR - (void)outputs; + for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++) + { + GPIOPin addrPin = {GPIOADDR, i}; + GPIO_SetDirection(addrPin, outputs & (1UL << i)); + } } /** Sets which pins on the 32-bit data bus should be outputs @@ -140,8 +149,11 @@ void ParallelBus_SetAddressDir(uint32_t outputs) */ void ParallelBus_SetDataDir(uint32_t outputs) { - // TODO: Set direction of GPIODATA - (void)outputs; + for (int i = 0; i < 32; i++) + { + GPIOPin dataPin = {GPIODATA, i}; + GPIO_SetDirection(dataPin, outputs & (1UL << i)); + } } /** Sets the direction of the CS pin @@ -189,8 +201,11 @@ void ParallelBus_SetWEDir(bool output) */ void ParallelBus_SetAddressPullups(uint32_t pullups) { - // TODO: Set pullups on GPIOADDR - (void)pullups; + for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++) + { + GPIOPin addrPin = {GPIOADDR, i}; + GPIO_SetPullup(addrPin, pullups & (1UL << i)); + } } /** Sets which pins on the 32-bit data bus should be pulled up (if inputs) @@ -203,8 +218,11 @@ void ParallelBus_SetAddressPullups(uint32_t pullups) */ void ParallelBus_SetDataPullups(uint32_t pullups) { - // TODO: Set pullups on GPIODATA - (void)pullups; + for (int i = 0; i < 32; i++) + { + GPIOPin dataPin = {GPIODATA, i}; + GPIO_SetPullup(dataPin, pullups & (1UL << i)); + } } /** Sets whether the CS pin is pulled up, if it's an input. @@ -252,8 +270,16 @@ void ParallelBus_SetWEPullup(bool pullup) */ uint32_t ParallelBus_ReadAddress(void) { - // TODO: Read GPIOADDR - return 0; + uint32_t readback = 0; + for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++) + { + GPIOPin addrPin = {GPIOADDR, i}; + if (GPIO_Read(addrPin)) + { + readback |= (1UL << i); + } + } + return readback; } /** Reads the current data on the 32-bit data bus. @@ -262,8 +288,16 @@ uint32_t ParallelBus_ReadAddress(void) */ uint32_t ParallelBus_ReadData(void) { - // TODO: Read GPIODATA - return 0; + uint32_t readback = 0; + for (int i = 0; i < 32; i++) + { + GPIOPin dataPin = {GPIODATA, i}; + if (GPIO_Read(dataPin)) + { + readback |= (1UL << i); + } + } + return readback; } /** Reads the status of the CS pin, if it's set as an input. diff --git a/hal/pc/pc_sources.cmake b/hal/pc/pc_sources.cmake index fb58012..bf53d54 100644 --- a/hal/pc/pc_sources.cmake +++ b/hal/pc/pc_sources.cmake @@ -1,7 +1,9 @@ # Create a list of all source files specific to the PC set(HWSOURCES hal/pc/board.c + hal/pc/flash_4mbit.c hal/pc/gpio.c + hal/pc/gpio_sim.c hal/pc/main.cpp hal/pc/mainwindow.cpp hal/pc/mainwindow.ui