diff --git a/configs/devices/arm-softmmu/default.mak b/configs/devices/arm-softmmu/default.mak
index 980c48a7d9..4f0f2e99c0 100644
--- a/configs/devices/arm-softmmu/default.mak
+++ b/configs/devices/arm-softmmu/default.mak
@@ -19,6 +19,7 @@ CONFIG_ARM_VIRT=y
 # CONFIG_NSERIES=n
 # CONFIG_STELLARIS=n
 # CONFIG_STM32VLDISCOVERY=n
+# CONFIG_STM32F1_GENERIC=n
 # CONFIG_REALVIEW=n
 # CONFIG_VERSATILE=n
 # CONFIG_VEXPRESS=n
diff --git a/docs/system/arm/stm32.rst b/docs/system/arm/stm32.rst
index d7265b763d..d0a3b1a7eb 100644
--- a/docs/system/arm/stm32.rst
+++ b/docs/system/arm/stm32.rst
@@ -10,6 +10,12 @@ The STM32F1 series is based on ARM Cortex-M3 core. The following machines are
 based on this chip :
 
 - ``stm32vldiscovery``  STM32VLDISCOVERY board with STM32F100RBT6 microcontroller
+- ``stm32f1-generic``   Generic STM32F1 board supporting low, medium and high
+                        density devices. Low-density emulates a 32KB FLASH;
+                        medium-density emulates a 128KB FLASH; high-density
+                        emulates a 512KB FLASH. The density also affects the
+                        number of peripherals exposed by QEMU for the emulated
+                        device. See ``Boot options`` below for more details.
 
 The STM32F2 series is based on ARM Cortex-M3 core. The following machines are
 based on this chip :
@@ -65,3 +71,11 @@ firmware. Example:
 .. code-block:: bash
 
   $ qemu-system-arm -M stm32vldiscovery -kernel firmware.bin
+
+Additionally, the ``stm32f1-generic`` board supports the ``density`` option
+to select the device density line.  The following values are supported:
+``low``, ``medium``, ``high``. Example:
+
+.. code-block:: bash
+
+  $ qemu-system-arm -M stm32f1-generic -global stm32f100-soc.density=medium ...
\ No newline at end of file
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 2159de3ce6..822441945c 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -301,6 +301,12 @@ config STM32VLDISCOVERY
     depends on TCG && ARM
     select STM32F100_SOC
 
+config STM32F1_GENERIC
+    bool
+    default y
+    depends on TCG && ARM
+    select STM32F100_SOC
+
 config STRONGARM
     bool
     select PXA2XX
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 870ec67376..f88b5fe3c8 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -23,6 +23,7 @@ arm_ss.add(when: 'CONFIG_REALVIEW', if_true: files('realview.c'))
 arm_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa-ref.c'))
 arm_ss.add(when: 'CONFIG_STELLARIS', if_true: files('stellaris.c'))
 arm_ss.add(when: 'CONFIG_STM32VLDISCOVERY', if_true: files('stm32vldiscovery.c'))
+arm_ss.add(when: 'CONFIG_STM32F1_GENERIC', if_true: files('stm32f1_generic.c'))
 arm_ss.add(when: 'CONFIG_COLLIE', if_true: files('collie.c'))
 arm_ss.add(when: 'CONFIG_VERSATILE', if_true: files('versatilepb.c'))
 arm_ss.add(when: 'CONFIG_VEXPRESS', if_true: files('vexpress.c'))
diff --git a/hw/arm/stm32f100_soc.c b/hw/arm/stm32f100_soc.c
index f7b344ba9f..c157ffd644 100644
--- a/hw/arm/stm32f100_soc.c
+++ b/hw/arm/stm32f100_soc.c
@@ -38,10 +38,11 @@
 
 static const uint32_t usart_addr[STM_NUM_USARTS] = { 0x40013800, 0x40004400,
     0x40004800 };
-static const uint32_t spi_addr[STM_NUM_SPIS] = { 0x40013000, 0x40003800 };
+static const uint32_t spi_addr[STM_NUM_SPIS] = { 0x40013000, 0x40003800,
+    0x40003C00 };
 
 static const int usart_irq[STM_NUM_USARTS] = {37, 38, 39};
-static const int spi_irq[STM_NUM_SPIS] = {35, 36};
+static const int spi_irq[STM_NUM_SPIS] = {35, 36, 51};
 
 static void stm32f100_soc_initfn(Object *obj)
 {
@@ -50,17 +51,21 @@ static void stm32f100_soc_initfn(Object *obj)
 
     object_initialize_child(obj, "armv7m", &s->armv7m, TYPE_ARMV7M);
 
+    /*
+     * All density lines feature the same number of USARTs, so they can be
+     * initialized in this function. The number of SPIs is density-dependent,
+     * though, so SPIs are initialized in stm32f100_soc_realize().
+     */
     for (i = 0; i < STM_NUM_USARTS; i++) {
         object_initialize_child(obj, "usart[*]", &s->usart[i],
                                 TYPE_STM32F2XX_USART);
     }
 
-    for (i = 0; i < STM_NUM_SPIS; i++) {
-        object_initialize_child(obj, "spi[*]", &s->spi[i], TYPE_STM32F2XX_SPI);
-    }
-
     s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
     s->refclk = qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0);
+
+    /* Default density. May be overridden by the machine or cmdline option */
+    s->density = STM32F100_DENSITY_HIGH;
 }
 
 static void stm32f100_soc_realize(DeviceState *dev_soc, Error **errp)
@@ -70,6 +75,17 @@ static void stm32f100_soc_realize(DeviceState *dev_soc, Error **errp)
     SysBusDevice *busdev;
     int i;
 
+    if (s->density == STM32F100_DENSITY_HIGH) {
+        s->num_spis = 3;
+        s->flash_size = FLASH_SIZE_HD;
+    } else if (s->density == STM32F100_DENSITY_MEDIUM) {
+        s->num_spis = 2;
+        s->flash_size = FLASH_SIZE_MD;
+    } else {
+        s->num_spis = 2;
+        s->flash_size = FLASH_SIZE_LD;
+    }
+
     MemoryRegion *system_memory = get_system_memory();
 
     /*
@@ -101,9 +117,10 @@ static void stm32f100_soc_realize(DeviceState *dev_soc, Error **errp)
      * Flash starts at 0x08000000 and then is aliased to boot memory at 0x0
      */
     memory_region_init_rom(&s->flash, OBJECT(dev_soc), "STM32F100.flash",
-                           FLASH_SIZE, &error_fatal);
+                           s->flash_size, &error_fatal);
     memory_region_init_alias(&s->flash_alias, OBJECT(dev_soc),
-                             "STM32F100.flash.alias", &s->flash, 0, FLASH_SIZE);
+                             "STM32F100.flash.alias", &s->flash, 0,
+                             s->flash_size);
     memory_region_add_subregion(system_memory, FLASH_BASE_ADDRESS, &s->flash);
     memory_region_add_subregion(system_memory, 0, &s->flash_alias);
 
@@ -137,8 +154,11 @@ static void stm32f100_soc_realize(DeviceState *dev_soc, Error **errp)
         sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, usart_irq[i]));
     }
 
-    /* SPI 1 and 2 */
-    for (i = 0; i < STM_NUM_SPIS; i++) {
+    /* Initialize all SPIs supported by the selected density line */
+    for (i = 0; i < s->num_spis; i++) {
+        object_initialize_child(OBJECT(dev_soc), "spi[*]", &s->spi[i],
+                                TYPE_STM32F2XX_SPI);
+
         dev = DEVICE(&(s->spi[i]));
         if (!sysbus_realize(SYS_BUS_DEVICE(&s->spi[i]), errp)) {
             return;
@@ -153,9 +173,14 @@ static void stm32f100_soc_realize(DeviceState *dev_soc, Error **errp)
     create_unimplemented_device("timer[4]",  0x40000800, 0x400);
     create_unimplemented_device("timer[6]",  0x40001000, 0x400);
     create_unimplemented_device("timer[7]",  0x40001400, 0x400);
+    create_unimplemented_device("timer[12]", 0x40001800, 0x400);
+    create_unimplemented_device("timer[13]", 0x40001C00, 0x400);
+    create_unimplemented_device("timer[14]", 0x40002000, 0x400);
     create_unimplemented_device("RTC",       0x40002800, 0x400);
     create_unimplemented_device("WWDG",      0x40002C00, 0x400);
     create_unimplemented_device("IWDG",      0x40003000, 0x400);
+    create_unimplemented_device("UART4",     0x40004C00, 0x400);
+    create_unimplemented_device("UART5",     0x40005000, 0x400);
     create_unimplemented_device("I2C1",      0x40005400, 0x400);
     create_unimplemented_device("I2C2",      0x40005800, 0x400);
     create_unimplemented_device("BKP",       0x40006C00, 0x400);
@@ -169,12 +194,15 @@ static void stm32f100_soc_realize(DeviceState *dev_soc, Error **errp)
     create_unimplemented_device("GPIOC",     0x40011000, 0x400);
     create_unimplemented_device("GPIOD",     0x40011400, 0x400);
     create_unimplemented_device("GPIOE",     0x40011800, 0x400);
+    create_unimplemented_device("GPIOF",     0x40011C00, 0x400);
+    create_unimplemented_device("GPIOG",     0x40012000, 0x400);
     create_unimplemented_device("ADC1",      0x40012400, 0x400);
     create_unimplemented_device("timer[1]",  0x40012C00, 0x400);
     create_unimplemented_device("timer[15]", 0x40014000, 0x400);
     create_unimplemented_device("timer[16]", 0x40014400, 0x400);
     create_unimplemented_device("timer[17]", 0x40014800, 0x400);
-    create_unimplemented_device("DMA",       0x40020000, 0x400);
+    create_unimplemented_device("DMA1",      0x40020000, 0x400);
+    create_unimplemented_device("DMA2",      0x40020400, 0x400);
     create_unimplemented_device("RCC",       0x40021000, 0x400);
     create_unimplemented_device("Flash Int", 0x40022000, 0x400);
     create_unimplemented_device("CRC",       0x40023000, 0x400);
@@ -185,12 +213,50 @@ static Property stm32f100_soc_properties[] = {
     DEFINE_PROP_END_OF_LIST(),
 };
 
-static void stm32f100_soc_class_init(ObjectClass *klass, void *data)
+static char *stm32f100_get_density(Object *obj, Error **errp)
 {
-    DeviceClass *dc = DEVICE_CLASS(klass);
+    STM32F100State *s = STM32F100_SOC(obj);
+
+    switch (s->density) {
+    case STM32F100_DENSITY_LOW:
+        return g_strdup("low");
+    case STM32F100_DENSITY_MEDIUM:
+        return g_strdup("medium");
+    case STM32F100_DENSITY_HIGH:
+        return g_strdup("high");
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static void stm32f100_set_density(Object *obj, const char *value, Error **errp)
+{
+    STM32F100State *s = STM32F100_SOC(obj);
+
+    if (!strcmp(value, "low")) {
+        s->density = STM32F100_DENSITY_LOW;
+    } else if (!strcmp(value, "medium")) {
+        s->density = STM32F100_DENSITY_MEDIUM;
+    } else if (!strcmp(value, "high")) {
+        s->density = STM32F100_DENSITY_HIGH;
+    } else {
+        error_setg(errp, "Invalid density value '%s'", value);
+        error_append_hint(errp, "Valid values: 'low', 'medium', 'high'\n");
+    }
+}
+
+static void stm32f100_soc_class_init(ObjectClass *oc, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(oc);
 
     dc->realize = stm32f100_soc_realize;
     device_class_set_props(dc, stm32f100_soc_properties);
+
+    object_class_property_add_str(oc, "density", stm32f100_get_density,
+        stm32f100_set_density);
+    object_class_property_set_description(oc, "density",
+        "Set the STM32F100 density line device. "
+        "Valid values are 'low', 'medium', and 'high' (default).");
 }
 
 static const TypeInfo stm32f100_soc_info = {
diff --git a/hw/arm/stm32f1_generic.c b/hw/arm/stm32f1_generic.c
new file mode 100644
index 0000000000..63d2a58bdc
--- /dev/null
+++ b/hw/arm/stm32f1_generic.c
@@ -0,0 +1,70 @@
+/*
+ * ST generic STM32F1 board
+ *
+ * Copyright (c) 2023 Lucas C. Villa Real <lucas@osdyne.com>
+ * Copyright (c) 2021 Alexandre Iooss <erdnaxe@crans.org>
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/boards.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-clock.h"
+#include "qemu/error-report.h"
+#include "hw/arm/stm32f100_soc.h"
+#include "hw/arm/boot.h"
+
+/* Main SYSCLK frequency in Hz (24MHz) */
+#define SYSCLK_FRQ 24000000ULL
+
+static void stm32f1_generic_init(MachineState *machine)
+{
+    STM32F100State *s;
+    DeviceState *dev;
+    Clock *sysclk;
+
+    /* This clock doesn't need migration because it is fixed-frequency */
+    sysclk = clock_new(OBJECT(machine), "SYSCLK");
+    clock_set_hz(sysclk, SYSCLK_FRQ);
+
+    /*
+     * Note that we don't set the "density" property so that the default
+     * value ("high") can be changed via "-global stm32f100-soc.density=..."
+     */
+    dev = qdev_new(TYPE_STM32F100_SOC);
+    qdev_prop_set_string(dev, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m3"));
+    qdev_connect_clock_in(dev, "sysclk", sysclk);
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+    s = STM32F100_SOC(OBJECT(dev));
+    armv7m_load_kernel(ARM_CPU(first_cpu),
+                       machine->kernel_filename,
+                       0, s->flash_size);
+}
+
+static void stm32f1_generic_machine_init(MachineClass *mc)
+{
+    mc->desc = "STM32F1 generic (Cortex-M3)";
+    mc->init = stm32f1_generic_init;
+}
+
+DEFINE_MACHINE("stm32f1-generic", stm32f1_generic_machine_init)
diff --git a/hw/arm/stm32vldiscovery.c b/hw/arm/stm32vldiscovery.c
index 67675e952f..3c4bffe5d4 100644
--- a/hw/arm/stm32vldiscovery.c
+++ b/hw/arm/stm32vldiscovery.c
@@ -47,13 +47,14 @@ static void stm32vldiscovery_init(MachineState *machine)
     clock_set_hz(sysclk, SYSCLK_FRQ);
 
     dev = qdev_new(TYPE_STM32F100_SOC);
+    qdev_prop_set_string(dev, "density", "medium");
     qdev_prop_set_string(dev, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m3"));
     qdev_connect_clock_in(dev, "sysclk", sysclk);
     sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
 
     armv7m_load_kernel(ARM_CPU(first_cpu),
                        machine->kernel_filename,
-                       0, FLASH_SIZE);
+                       0, FLASH_SIZE_MD);
 }
 
 static void stm32vldiscovery_machine_init(MachineClass *mc)
diff --git a/include/hw/arm/stm32f100_soc.h b/include/hw/arm/stm32f100_soc.h
index 40cd415b28..5305e342e3 100644
--- a/include/hw/arm/stm32f100_soc.h
+++ b/include/hw/arm/stm32f100_soc.h
@@ -34,14 +34,24 @@
 #define TYPE_STM32F100_SOC "stm32f100-soc"
 OBJECT_DECLARE_SIMPLE_TYPE(STM32F100State, STM32F100_SOC)
 
+/* Definitions for high-density value line devices */
 #define STM_NUM_USARTS 3
-#define STM_NUM_SPIS 2
+#define STM_NUM_SPIS 3
 
 #define FLASH_BASE_ADDRESS 0x08000000
-#define FLASH_SIZE (128 * 1024)
+#define FLASH_SIZE_LD (32 * 1024)
+#define FLASH_SIZE_MD (128 * 1024)
+#define FLASH_SIZE_HD (512 * 1024)
 #define SRAM_BASE_ADDRESS 0x20000000
 #define SRAM_SIZE (8 * 1024)
 
+/* Supported density value lines */
+typedef enum {
+    STM32F100_DENSITY_LOW,
+    STM32F100_DENSITY_MEDIUM,
+    STM32F100_DENSITY_HIGH,
+} STM32F100Density;
+
 struct STM32F100State {
     /*< private >*/
     SysBusDevice parent_obj;
@@ -60,6 +70,10 @@ struct STM32F100State {
 
     Clock *sysclk;
     Clock *refclk;
+
+    STM32F100Density density;
+    uint8_t num_spis;
+    uint32_t flash_size;
 };
 
 #endif
