├── .github
├── CODEOWNERS
└── workflows
│ └── dotnetcore.yml
├── .gitignore
├── Essenbee.Spectrum48
├── App.config
├── Essenbee.Spectrum48.csproj
├── Libs
│ ├── Licences.txt
│ └── PixelEngine.dll
├── Program.cs
├── Properties
│ └── AssemblyInfo.cs
├── ROM
│ └── 48.rom
├── SimpleBus.cs
├── Z80FileReader.cs
└── Z80Snapshot.cs
├── Essenbee.Z80.Debugger
├── App.xaml
├── App.xaml.cs
├── BasicBus.cs
├── ControlPanel.xaml
├── ControlPanel.xaml.cs
├── CpuFlags.xaml
├── CpuFlags.xaml.cs
├── CpuStatus.xaml
├── CpuStatus.xaml.cs
├── DependencyProperties.ttinclude
├── Disassembler.xaml
├── Disassembler.xaml.cs
├── Essenbee.Z80.Debugger.csproj
├── Generated_DependencyProperties.cs
├── Generated_DependencyProperties.tt
├── Generated_ViewModel.cs
├── Generated_ViewModel.tt
├── Header.ttinclude
├── HeaderCommon.ttinclude
├── HexFileLoader.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── MainWindowViewModel.cs
├── MemoryMap.xaml
├── MemoryMap.xaml.cs
├── MsxScreen.xaml
├── MsxScreen.xaml.cs
├── ObservableDictionary.cs
├── ROM
│ ├── 48.rom
│ └── 48.rom.data
├── StackDisplay.xaml
├── StackDisplay.xaml.cs
└── UserCommand.cs
├── Essenbee.Z80.Sample
├── Essenbee.Z80.Sample.csproj
├── Program.cs
├── ROM
│ └── 48.rom
└── SimpleBus.cs
├── Essenbee.Z80.Tests
├── BinaryCodedDecimalArithmeticTests.cs
├── CallAndReturnGroupShould.cs
├── Classes
│ ├── BasicBus.cs
│ ├── FuseTester.cs
│ ├── HexFileReader.cs
│ └── Results.cs
├── ComparisonsShould.cs
├── DisassemblerShould.cs
├── EightBitArithmeticLogicADDGroupShould.cs
├── EightBitArithmeticLogicSUBGroupShould.cs
├── EightBitLoadGroupShould.cs
├── EightBitLogicGroupShould.cs
├── Essenbee.Z80.Tests.csproj
├── ExchangeShould.cs
├── FlagsShould.cs
├── FuseTests
│ ├── FUSE_Expected.txt
│ └── FUSE_Tests.txt
├── GeneralControlGroupShould.cs
├── HexFileReaderTests.cs
├── HexFiles
│ ├── Arithmetic1.hex
│ ├── ExampleMonitor.hex
│ ├── HexFileFormat.txt
│ ├── Multiplication.hex
│ ├── Multiplication2.hex
│ └── TestDDCBandFDCB.hex
├── InputOutputShould.cs
├── IsSupportedShould.cs
├── JumpGroupShould.cs
├── RotateAndShiftGroupShould.cs
├── SixteenBitArithmeticLogicGroupShould.cs
├── SixteenBitLoadGroupShould.cs
└── Z80EmulatorShould.cs
├── Essenbee.Z80
├── .cr
│ └── images
│ │ ├── CF3C03E9AEA956395291BE98C580C27F.png
│ │ └── E5D9CCCCCD8B82BB1159F822CFE086F8.png
├── Essenbee.Z80.csproj
├── Essenbee.Z80.sln
├── IBus.cs
├── Instruction.cs
├── InterruptMode.cs
├── Z80.AddressModes.cs
├── Z80.ArithmeticLogic.cs
├── Z80.BitGroup.cs
├── Z80.Helpers.cs
├── Z80.IO.cs
├── Z80.Instructions.cs
├── Z80.Jump.cs
├── Z80.Load.cs
├── Z80.ShiftRotate.cs
└── Z80.cs
├── LICENSE
└── README.md
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @essenbee
2 |
--------------------------------------------------------------------------------
/.github/workflows/dotnetcore.yml:
--------------------------------------------------------------------------------
1 | name: .NET Core
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: windows-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Setup .NET Core
13 | uses: actions/setup-dotnet@v1
14 | with:
15 | dotnet-version: 3.1.100
16 | - name: Build with dotnet
17 | run: dotnet build ./Essenbee.Z80 --configuration Release
18 | - name: Tests
19 | run: dotnet test ./Essenbee.Z80.Tests
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------
/Essenbee.Spectrum48/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Essenbee.Spectrum48/Essenbee.Spectrum48.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {0B15FF0C-414B-4F4F-81E2-694074257B3B}
8 | Exe
9 | Essenbee.Spectrum48
10 | Essenbee.Spectrum48
11 | v4.6.1
12 | 512
13 | true
14 | true
15 |
16 |
17 |
18 | AnyCPU
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | AnyCPU
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 |
38 | False
39 | Libs\PixelEngine.dll
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | {7D5005D4-A8F1-4E80-8689-8DDD04264F3A}
67 | Essenbee.Z80
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/Essenbee.Spectrum48/Libs/Licences.txt:
--------------------------------------------------------------------------------
1 | License (OLC-3)
2 | ~~~~~~~~~~~~~~~
3 | Copyright 2018 OneLoneCoder.com
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 | 1. Redistributions or derivations of source code must retain the above
8 | copyright notice, this list of conditions and the following disclaimer.
9 | 2. Redistributions or derivative works in binary form must reproduce
10 | the above copyright notice. This list of conditions and the following
11 | disclaimer must be reproduced in the documentation and/or other
12 | materials provided with the distribution.
13 | 3. Neither the name of the copyright holder nor the names of its
14 | contributors may be used to endorse or promote products derived
15 | from this software without specific prior written permission.
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/Essenbee.Spectrum48/Libs/PixelEngine.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/essenbee/z80emu/8085dc449deb35d8b76ed76e26c4b1347e1286eb/Essenbee.Spectrum48/Libs/PixelEngine.dll
--------------------------------------------------------------------------------
/Essenbee.Spectrum48/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Essenbee.Spectrum48")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Essenbee.Spectrum48")]
13 | [assembly: AssemblyCopyright("Copyright © 2020")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("0b15ff0c-414b-4f4f-81e2-694074257b3b")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Essenbee.Spectrum48/ROM/48.rom:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/essenbee/z80emu/8085dc449deb35d8b76ed76e26c4b1347e1286eb/Essenbee.Spectrum48/ROM/48.rom
--------------------------------------------------------------------------------
/Essenbee.Spectrum48/SimpleBus.cs:
--------------------------------------------------------------------------------
1 | using PixelEngine;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace Essenbee.Z80.Spectrum48
6 | {
7 | public class SimpleBus : IBus
8 | {
9 | public bool ScreenReady { get; set; }
10 | public bool SoundOn { get; set; }
11 | public Pixel BorderColour { get; set; } = Pixel.Presets.Black;
12 |
13 | public int[] KeyMatrix { get; set; } = new int[8];
14 | public bool Interrupt { get; set; }
15 | public bool NonMaskableInterrupt { get; set; }
16 | public IList Data { get; set; } = new List();
17 |
18 | private byte[] _memory;
19 | private int _lineRendered = 0;
20 | private int _pixelLine = 0;
21 | private int _screenLine = 0;
22 | private Sprite _screenBuffer = new Sprite(32 * 8, 24 * 8);
23 | private const ushort _screenStart = 0x4000; // Beginning of Spectrum video RAM
24 | private const ushort _attributeStart = 0x5800; // 768 bytes of colour attributes
25 | private int _ink0, _ink1, _ink2;
26 | private int _paper0, _paper1, _paper2;
27 | private int _bright0, _bright1, _bright2;
28 | private int _flash0, _flash1, _flash2;
29 | private int _frameCounter;
30 |
31 | public SimpleBus(byte[] ram) => _memory = ram;
32 |
33 | public IReadOnlyCollection RAM
34 | => _memory;
35 |
36 | public byte Read(ushort addr, bool ro = false) => _memory[addr];
37 |
38 | public byte ReadPeripheral(ushort port)
39 | {
40 | // Console.WriteLine($"IN 0x{port:X4}");
41 |
42 | if ((port & 1) == 0)
43 | {
44 | // Keyboard handling..
45 | byte result = 0xFF;
46 | var keyRow = (port & 0xFF00) >> 8;
47 |
48 | if (keyRow == 0x7F)
49 | {
50 | result &= (byte)KeyMatrix[7];
51 | }
52 |
53 | if (keyRow == 0xBF)
54 | {
55 | result &= (byte)KeyMatrix[6];
56 | }
57 |
58 | if (keyRow == 0xDF)
59 | {
60 | result &= (byte)KeyMatrix[5];
61 | }
62 |
63 | if (keyRow == 0xEF)
64 | {
65 | result &= (byte)KeyMatrix[4];
66 | }
67 |
68 | if (keyRow == 0xF7)
69 | {
70 | result &= (byte)KeyMatrix[3];
71 | }
72 |
73 | if (keyRow == 0xFB)
74 | {
75 | result &= (byte)KeyMatrix[2];
76 | }
77 |
78 | if (keyRow == 0xFD)
79 | {
80 | result &= (byte)KeyMatrix[1];
81 | }
82 |
83 | if (keyRow == 0xFE)
84 | {
85 | result &= (byte)KeyMatrix[0];
86 | }
87 |
88 | result &= 0x1F; //mask out bits 0 to 4
89 | result |= 0b11100000; //set bit 5 - 7
90 | //Console.WriteLine($">>>>>>> Sending {Convert.ToString(result ,2)}");
91 |
92 | return result;
93 | }
94 |
95 | return 0;
96 | }
97 |
98 | public void Write(ushort addr, byte data) => _memory[addr] = data;
99 |
100 | public void WritePeripheral(ushort port, byte data)
101 | {
102 | // Console.WriteLine($"OUT 0x{port:X4} Data 0x{data:X2}");
103 |
104 | //` Bit 7 6 5 4 3 2 1 0
105 | //` +-------------------------------+
106 | //` | | | | E | M | Border |
107 | //` +-------------------------------+
108 |
109 | // ULA select
110 | if ((port & 0x00FF) == 0xFE)
111 | {
112 | // Set border colour (0 - 7)
113 | if (port >> 8 < 8)
114 | {
115 | var borderColour = data & 0b00000111;
116 | BorderColour = GetColouredPixel(borderColour, 0);
117 | return;
118 | }
119 |
120 | if ((port & 0b00001000) > 1)
121 | {
122 | // Activate MIC
123 | }
124 |
125 | if ((port & 0b00010000) > 1)
126 | {
127 | // Activate EAR
128 | SoundOn = true;
129 | }
130 | else
131 | {
132 | SoundOn = false;
133 | }
134 | }
135 | }
136 |
137 | public Sprite GetScreen() => _screenBuffer;
138 |
139 | public void RunRenderer()
140 | {
141 | StepRenderer();
142 |
143 | if (ScreenReady)
144 | {
145 | Interrupt = true; // 50 Hz maskable interrupt
146 |
147 | // If interrupt Mode is 2:-
148 | // The Spectrum didn’t use IM 2 normally; it was widely assumed that cannot
149 | // guarantee what is on the data bus when the interrupt occurred, so programmers
150 | // tend to generate a vector table with 128 addresses, all pointing to the routine.
151 | // The data bus will read 0xFF in this case.
152 | Data = new List { 0xFF };
153 | }
154 | }
155 |
156 | private void StepRenderer()
157 | {
158 | var offset = _screenStart + (_pixelLine << 8) + (_screenLine << 5);
159 | // Top third of screen
160 | var memOffset0 = (0 << 11) + offset;
161 | // Middle third of screen
162 | var memOffset1 = (1 << 11) + offset;
163 | // Bottom third of screen
164 | var memOffset2 = (2 << 11) + offset;
165 |
166 | for (var c = 0; c < 32; c++)
167 | {
168 | var c0 = _memory[memOffset0 + c]; // Get byte value 0
169 | var c1 = _memory[memOffset1 + c]; // Get byte value 1
170 | var c2 = _memory[memOffset2 + c]; // Get byte value 2
171 |
172 | ReadAttributesForCharacter(c);
173 |
174 | // Decode bits
175 | for (var bitPos = 0; bitPos < 8; ++bitPos)
176 | {
177 | var b0 = 0x1 & (c0 >> (7 - bitPos));
178 | var b1 = 0x1 & (c1 >> (7 - bitPos));
179 | var b2 = 0x1 & (c2 >> (7 - bitPos));
180 | var x = (c * 8) + bitPos;
181 |
182 | _screenBuffer[x, _lineRendered + 0] = GetPixel(b0 != 0, _ink0, _paper0, _bright0, _flash0 == 1);
183 | _screenBuffer[x, _lineRendered + 64] = GetPixel(b1 != 0, _ink1, _paper1, _bright1, _flash1 == 1);
184 | _screenBuffer[x, _lineRendered + 128] = GetPixel(b2 != 0, _ink2, _paper2, _bright2, _flash2 == 1);
185 | }
186 | }
187 |
188 | _lineRendered++;
189 | _pixelLine++;
190 |
191 | if (_pixelLine > 7)
192 | {
193 | _pixelLine = 0;
194 | _screenLine++;
195 | }
196 |
197 | if (_screenLine > 7)
198 | {
199 | _screenLine = 0; ;
200 | }
201 |
202 | if (_lineRendered > 63)
203 | {
204 | _lineRendered = 0;
205 | ScreenReady = true;
206 | _frameCounter++;
207 | }
208 | }
209 |
210 | private void ReadAttributesForCharacter(int c)
211 | {
212 | var offset = _attributeStart + (_screenLine << 5);
213 | var attrOffset0 = (0 << 8) + offset + c;
214 | var attrOffset1 = (1 << 8) + offset + c;
215 | var attrOffset2 = (2 << 8) + offset + c;
216 |
217 | var attr0 = _memory[attrOffset0];
218 | var attr1 = _memory[attrOffset1];
219 | var attr2 = _memory[attrOffset2];
220 |
221 | _ink0 = attr0 & 0b00000111;
222 | _paper0 = (attr0 & 0b00111000) >> 3;
223 | _bright0 = (attr0 & 0b01000000) >> 6;
224 | _flash0 = (attr0 & 0b10000000) >> 7;
225 |
226 | _ink1 = attr1 & 0b00000111;
227 | _paper1 = (attr1 & 0b00111000) >> 3;
228 | _bright1 = (attr1 & 0b01000000) >> 6;
229 | _flash1 = (attr1 & 0b10000000) >> 7;
230 |
231 | _ink2 = attr2 & 0b00000111;
232 | _paper2 = (attr2 & 0b00111000) >> 3;
233 | _bright2 = (attr2 & 0b01000000) >> 6;
234 | _flash2 = (attr2 & 0b10000000) >> 7;
235 | }
236 | private Pixel GetPixel(bool foreground, int ink, int paper, int brightness, bool flashing)
237 | {
238 | if (flashing)
239 | {
240 | if (_frameCounter < 17)
241 | {
242 | return NormalPixel(foreground, ink, paper, brightness);
243 | }
244 |
245 | if (_frameCounter > 32)
246 | {
247 | _frameCounter = 0;
248 | }
249 |
250 | return InvertedPixel(foreground, ink, paper, brightness);
251 | }
252 |
253 | return NormalPixel(foreground, ink, paper, brightness);
254 | }
255 |
256 | private Pixel InvertedPixel(bool foreground, int ink, int paper, int brightness) =>
257 | foreground ? GetColouredPixel(paper, brightness) : GetColouredPixel(ink, brightness);
258 |
259 | private Pixel NormalPixel(bool foreground, int ink, int paper, int brightness) =>
260 | foreground ? GetColouredPixel(ink, brightness) : GetColouredPixel(paper, brightness);
261 |
262 | private Pixel GetColouredPixel(int colour, int brightness)
263 | {
264 | if (brightness == 0)
265 | {
266 | switch (colour)
267 | {
268 | case 0: return Pixel.Presets.Black;
269 | case 1: return Pixel.Presets.DarkBlue;
270 | case 2: return Pixel.Presets.DarkRed;
271 | case 3: return Pixel.Presets.DarkMagenta;
272 | case 4: return Pixel.Presets.DarkGreen;
273 | case 5: return Pixel.Presets.DarkCyan;
274 | case 6: return Pixel.Presets.DarkYellow;
275 | default: return Pixel.Presets.Grey;
276 | }
277 | }
278 | else
279 | {
280 | switch (colour)
281 | {
282 | case 0: return Pixel.Presets.Black;
283 | case 1: return Pixel.Presets.Blue;
284 | case 2: return Pixel.Presets.Red;
285 | case 3: return Pixel.Presets.Magenta;
286 | case 4: return Pixel.Presets.Green;
287 | case 5: return Pixel.Presets.Cyan;
288 | case 6: return Pixel.Presets.Yellow;
289 | default: return Pixel.Presets.White;
290 | }
291 | }
292 | }
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/Essenbee.Spectrum48/Z80Snapshot.cs:
--------------------------------------------------------------------------------
1 | namespace Essenbee.Z80.Spectrum48
2 | {
3 | public class Z80Snapshot
4 | {
5 | public int Type;
6 | public byte I;
7 | public int HL1, DE1, BC1, AF1;
8 | public int HL, DE, BC, IX, IY;
9 | public byte R;
10 | public int AF, SP;
11 | public byte IM;
12 | public byte Border;
13 | public int PC;
14 | public byte Port7FFD;
15 | public byte PortFFFD;
16 | public byte Port1FFD;
17 | public byte[] AYRegisters;
18 | public bool IFF1;
19 | public bool IFF2;
20 | public bool IsIssue2;
21 | public bool AY48K;
22 | public int TStates;
23 | public byte[][] RAMBank = new byte[16][];
24 | public byte[] Spectrum48 = new byte[49152];
25 | }
26 | }
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace Essenbee.Z80.Debugger
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/BasicBus.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Essenbee.Z80.Debugger
5 | {
6 | public class BasicBus : IBus
7 | {
8 | public bool Interrupt { get; set; }
9 | public bool NonMaskableInterrupt { get; set; }
10 | public IList Data { get; set; } = new List();
11 |
12 | private byte[] _memory;
13 | public BasicBus(int RAMSize)
14 | {
15 | _memory = new byte[RAMSize * 1024];
16 | }
17 |
18 | public BasicBus(byte[] ram)
19 | {
20 | _memory = ram;
21 | }
22 |
23 | public IReadOnlyCollection RAM
24 | {
25 | get => _memory;
26 | }
27 |
28 | public byte Read(ushort addr, bool ro = false)
29 | {
30 | return _memory[addr];
31 | }
32 |
33 | public byte ReadPeripheral(ushort port)
34 | {
35 |
36 | Console.WriteLine($"IN 0x{port:X4}");
37 |
38 | return 0;
39 | }
40 |
41 | public void Write(ushort addr, byte data)
42 | {
43 | _memory[addr] = data;
44 | }
45 |
46 | public void WritePeripheral(ushort port, byte data)
47 | {
48 | Console.WriteLine($"OUT 0x{port:X4}, 0x{data:X2}");
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/ControlPanel.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
18 |
25 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/ControlPanel.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Data;
7 | using System.Windows.Documents;
8 | using System.Windows.Input;
9 | using System.Windows.Media;
10 | using System.Windows.Media.Imaging;
11 | using System.Windows.Navigation;
12 | using System.Windows.Shapes;
13 |
14 | namespace Essenbee.Z80.Debugger
15 | {
16 | ///
17 | /// Interaction logic for ControlPanel.xaml
18 | ///
19 | public partial class ControlPanel : UserControl
20 | {
21 | public ControlPanel()
22 | {
23 | InitializeComponent();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/CpuFlags.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
13 |
15 |
16 |
17 |
20 |
25 |
26 |
27 |
30 |
35 |
36 |
37 |
40 |
45 |
46 |
47 |
50 |
55 |
56 |
57 |
60 |
65 |
66 |
67 |
70 |
75 |
76 |
77 |
80 |
85 |
86 |
87 |
88 |
91 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/CpuFlags.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Data;
7 | using System.Windows.Documents;
8 | using System.Windows.Input;
9 | using System.Windows.Media;
10 | using System.Windows.Media.Imaging;
11 | using System.Windows.Navigation;
12 | using System.Windows.Shapes;
13 |
14 | namespace Essenbee.Z80.Debugger
15 | {
16 | ///
17 | /// Interaction logic for CpuFlags.xaml
18 | ///
19 | public partial class CpuFlags : UserControl
20 | {
21 | public CpuFlags()
22 | {
23 | InitializeComponent();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/CpuStatus.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
24 |
27 |
32 |
33 |
34 |
35 |
38 |
43 |
46 |
51 |
52 |
53 |
54 |
57 |
62 |
65 |
70 |
71 |
72 |
73 |
76 |
81 |
84 |
89 |
90 |
91 |
92 |
95 |
100 |
103 |
108 |
109 |
110 |
111 |
114 |
119 |
122 |
127 |
128 |
129 |
130 |
133 |
138 |
141 |
146 |
147 |
150 |
156 |
157 |
158 |
159 |
160 |
163 |
169 |
172 |
178 |
179 |
180 |
181 |
182 |
183 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/CpuStatus.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Data;
7 | using System.Windows.Documents;
8 | using System.Windows.Input;
9 | using System.Windows.Media;
10 | using System.Windows.Media.Imaging;
11 | using System.Windows.Navigation;
12 | using System.Windows.Shapes;
13 |
14 | namespace Essenbee.Z80.Debugger
15 | {
16 | ///
17 | /// Interaction logic for CpuStatus.xaml
18 | ///
19 | public partial class CpuStatus : UserControl
20 | {
21 | public CpuStatus()
22 | {
23 | InitializeComponent();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/Disassembler.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
22 |
23 |
30 |
31 |
32 |
37 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/Disassembler.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Data;
7 | using System.Windows.Documents;
8 | using System.Windows.Input;
9 | using System.Windows.Media;
10 | using System.Windows.Media.Imaging;
11 | using System.Windows.Navigation;
12 | using System.Windows.Shapes;
13 |
14 | namespace Essenbee.Z80.Debugger
15 | {
16 | ///
17 | /// Interaction logic for Disassembler.xaml
18 | ///
19 | public partial class Disassembler : UserControl
20 | {
21 | public Disassembler()
22 | {
23 | InitializeComponent();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/Essenbee.Z80.Debugger.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | netcoreapp3.0
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | TextTemplatingFileGenerator
16 | Generated_DependencyProperties.cs
17 |
18 |
19 | TextTemplatingFileGenerator
20 | Generated_ViewModel.cs
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | True
31 | True
32 | Generated_DependencyProperties.tt
33 |
34 |
35 | True
36 | True
37 | Generated_ViewModel.tt
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/Generated_DependencyProperties.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 |
8 |
9 | // ############################################################################
10 | // # #
11 | // # ---==> T H I S F I L E I S G E N E R A T E D <==--- #
12 | // # #
13 | // # This means that any edits to the .cs file will be lost when its #
14 | // # regenerated. Changes should instead be applied to the corresponding #
15 | // # template file (.tt) #
16 | // ############################################################################
17 |
18 |
19 |
20 |
21 |
22 |
23 | // ReSharper disable CompareOfFloatsByEqualityOperator
24 | // ReSharper disable InconsistentNaming
25 | // ReSharper disable InvocationIsSkipped
26 | // ReSharper disable PartialMethodWithSinglePart
27 | // ReSharper disable PartialTypeWithSinglePart
28 | // ReSharper disable PossibleUnintendedReferenceComparison
29 | // ReSharper disable RedundantAssignment
30 | // ReSharper disable RedundantCast
31 | // ReSharper disable RedundantUsingDirective
32 | // ReSharper disable UnusedMember.Local
33 |
34 | namespace Essenbee.Z80.Debugger
35 | {
36 | using System.Collections;
37 | using System.Collections.ObjectModel;
38 | using System.Collections.Specialized;
39 |
40 | using System.Windows;
41 | using System.Windows.Media;
42 |
43 | // ------------------------------------------------------------------------
44 | // MsxScreen
45 | // ------------------------------------------------------------------------
46 | partial class MsxScreen
47 | {
48 | #region Uninteresting generated code
49 | public static readonly DependencyProperty ScreenProperty = DependencyProperty.Register (
50 | "Screen",
51 | typeof (int),
52 | typeof (MsxScreen),
53 | new FrameworkPropertyMetadata (
54 | 0x4000,
55 | FrameworkPropertyMetadataOptions.AffectsRender,
56 | Changed_Screen,
57 | Coerce_Screen
58 | ));
59 |
60 | static void Changed_Screen (DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
61 | {
62 | var instance = dependencyObject as MsxScreen;
63 | if (instance != null)
64 | {
65 | var oldValue = (int)eventArgs.OldValue;
66 | var newValue = (int)eventArgs.NewValue;
67 |
68 | instance.Changed_Screen (oldValue, newValue);
69 | }
70 | }
71 |
72 |
73 | static object Coerce_Screen (DependencyObject dependencyObject, object basevalue)
74 | {
75 | var instance = dependencyObject as MsxScreen;
76 | if (instance == null)
77 | {
78 | return basevalue;
79 | }
80 | var value = (int)basevalue;
81 |
82 | instance.Coerce_Screen (ref value);
83 |
84 |
85 | return value;
86 | }
87 |
88 | public static readonly DependencyProperty RawMemoryProperty = DependencyProperty.Register (
89 | "RawMemory",
90 | typeof (IReadOnlyCollection),
91 | typeof (MsxScreen),
92 | new FrameworkPropertyMetadata (
93 | null,
94 | FrameworkPropertyMetadataOptions.AffectsRender,
95 | Changed_RawMemory,
96 | Coerce_RawMemory
97 | ));
98 |
99 | static void Changed_RawMemory (DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
100 | {
101 | var instance = dependencyObject as MsxScreen;
102 | if (instance != null)
103 | {
104 | var oldValue = (IReadOnlyCollection)eventArgs.OldValue;
105 | var newValue = (IReadOnlyCollection)eventArgs.NewValue;
106 |
107 | instance.Changed_RawMemory (oldValue, newValue);
108 | }
109 | }
110 |
111 |
112 | static object Coerce_RawMemory (DependencyObject dependencyObject, object basevalue)
113 | {
114 | var instance = dependencyObject as MsxScreen;
115 | if (instance == null)
116 | {
117 | return basevalue;
118 | }
119 | var value = (IReadOnlyCollection)basevalue;
120 |
121 | instance.Coerce_RawMemory (ref value);
122 |
123 |
124 | return value;
125 | }
126 |
127 | #endregion
128 |
129 | // --------------------------------------------------------------------
130 | // Constructor
131 | // --------------------------------------------------------------------
132 | public MsxScreen ()
133 | {
134 | CoerceAllProperties ();
135 | Constructed__MsxScreen ();
136 | }
137 | // --------------------------------------------------------------------
138 | partial void Constructed__MsxScreen ();
139 | // --------------------------------------------------------------------
140 | void CoerceAllProperties ()
141 | {
142 | CoerceValue (ScreenProperty);
143 | CoerceValue (RawMemoryProperty);
144 | }
145 |
146 |
147 | // --------------------------------------------------------------------
148 | // Properties
149 | // --------------------------------------------------------------------
150 |
151 |
152 | // --------------------------------------------------------------------
153 | public int Screen
154 | {
155 | get
156 | {
157 | return (int)GetValue (ScreenProperty);
158 | }
159 | set
160 | {
161 | if (Screen != value)
162 | {
163 | SetValue (ScreenProperty, value);
164 | }
165 | }
166 | }
167 | // --------------------------------------------------------------------
168 | partial void Changed_Screen (int oldValue, int newValue);
169 | partial void Coerce_Screen (ref int coercedValue);
170 | // --------------------------------------------------------------------
171 |
172 |
173 |
174 | // --------------------------------------------------------------------
175 | public IReadOnlyCollection RawMemory
176 | {
177 | get
178 | {
179 | return (IReadOnlyCollection)GetValue (RawMemoryProperty);
180 | }
181 | set
182 | {
183 | if (RawMemory != value)
184 | {
185 | SetValue (RawMemoryProperty, value);
186 | }
187 | }
188 | }
189 | // --------------------------------------------------------------------
190 | partial void Changed_RawMemory (IReadOnlyCollection oldValue, IReadOnlyCollection newValue);
191 | partial void Coerce_RawMemory (ref IReadOnlyCollection coercedValue);
192 | // --------------------------------------------------------------------
193 |
194 |
195 | }
196 | // ------------------------------------------------------------------------
197 |
198 | }
199 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/Generated_DependencyProperties.tt:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | <#
8 | Namespace = "Essenbee.Z80.Debugger";
9 | Model = new []
10 | {
11 | new ClassDefinition ("MsxScreen")
12 | {
13 | P ("int" , "Screen" , defaultValue:"0x4000" , metaDataOptions: "FrameworkPropertyMetadataOptions.AffectsRender"),
14 | P ("IReadOnlyCollection" , "RawMemory" , defaultValue:"null" , metaDataOptions: "FrameworkPropertyMetadataOptions.AffectsRender"),
15 | },
16 | };
17 |
18 | #>
19 |
20 |
21 | <#@ include file ="DependencyProperties.ttinclude" #>
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/Generated_ViewModel.tt:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using System.ComponentModel;
4 | using System.Collections.Generic;
5 | using System.Windows;
6 | using System.Windows.Input;
7 | using System.Windows.Threading;
8 |
9 | <#
10 | // Model defines what properties and commands are generated
11 |
12 | var properties = new []
13 | {
14 | P ( "string" , "ProgramCounter" ),
15 | P ( "bool" , "SignBit" ),
16 | P ( "bool" , "ZeroBit" ),
17 | P ( "bool" , "UBit" ),
18 | P ( "bool" , "HalfCarryBit" ),
19 | P ( "bool" , "XBit" ),
20 | P ( "bool" , "ParityOverflowBit" ),
21 | P ( "bool" , "NegationBit" ),
22 | P ( "bool" , "CarryBit" ),
23 | P ( "string" , "AccuFlags" ),
24 | P ( "string" , "AccuFlagsPrime" ),
25 | P ( "string" , "HLPair" ),
26 | P ( "string" , "HLPairPrime" ),
27 | P ( "string" , "BCPair" ),
28 | P ( "string" , "BCPairPrime" ),
29 | P ( "string" , "DEPair" ),
30 | P ( "string" , "DEPairPrime" ),
31 | P ( "string" , "StackPointer" ),
32 | P ( "string" , "IndexX" ),
33 | P ( "string" , "IndexY" ),
34 | P ( "string" , "InterruptVector" ),
35 | P ( "int" , "Mode" ),
36 | P ( "string" , "Refresh" ),
37 | P ( "string" , "QRegister" ),
38 | P ( "string" , "MemPointer" ),
39 | P ( "Dictionary" , "Memory" ),
40 | P ( "int" , "MemoryMapRow" ),
41 | P ( "Dictionary" , "DisAsm" ),
42 | P ( "string" , "SelectedRow" ),
43 | P ( "IReadOnlyCollection" , "RawMemory" ),
44 | };
45 |
46 | var commands = new []
47 | {
48 | C ( "StepCommand" ),
49 | C ( "LoadCommand" ),
50 | C ( "LoadRomCommand" ),
51 | };
52 |
53 | #>
54 |
55 |
56 | <#
57 | // The template generates the code from the model
58 | #>
59 |
60 | namespace Essenbee.Z80.Debugger
61 | {
62 | public partial class MainWindowViewModel : INotifyPropertyChanged
63 | {
64 | readonly Dispatcher _dispatcher;
65 |
66 | public event PropertyChangedEventHandler PropertyChanged;
67 |
68 | <#
69 | foreach (var prop in properties)
70 | {
71 | if (prop.IsCollection)
72 | {
73 | #>
74 | // --------------------------------------------------------------------
75 | // BEGIN_COLLECTION_PROPERTY: <#=prop.Name#> (<#=prop.Type#>)
76 | // --------------------------------------------------------------------
77 | ObservableCollection<<#=prop.Type#>> _<#=prop.Name#> = new ObservableCollection<<#=prop.Type#>> ();
78 |
79 | void Raise_<#=prop.Name#> ()
80 | {
81 | <#
82 | if (prop.LabelValue.Length > 0)
83 | {
84 | #>
85 | OnPropertyChanged ("<#=prop.Name#>Label");
86 | <#
87 | }
88 | #>
89 | }
90 |
91 | <#
92 | if (prop.LabelValue.Length > 0)
93 | {
94 | #>
95 | public string <#=prop.Name#>Label => <#=prop.LabelValue#>;
96 |
97 | <#
98 | }
99 | #>
100 | public ObservableCollection<<#=prop.Type#>> <#=prop.Name#>
101 | {
102 | get { return _<#=prop.Name#>; }
103 | }
104 | // --------------------------------------------------------------------
105 | // END_COLLECTION_PROPERTY: <#=prop.Name#> (<#=prop.Type#>)
106 | // --------------------------------------------------------------------
107 |
108 | <#
109 | }
110 | else
111 | {
112 | #>
113 | // --------------------------------------------------------------------
114 | // BEGIN_PROPERTY: <#=prop.Name#> (<#=prop.Type#>)
115 | // --------------------------------------------------------------------
116 | <#=prop.Type#> _<#=prop.Name#> = <#=prop.DefaultValue#>;
117 |
118 | void Raise_<#=prop.Name#> ()
119 | {
120 | OnPropertyChanged ("<#=prop.Name#>");
121 | <#
122 | if (prop.LabelValue.Length > 0)
123 | {
124 | #>
125 | OnPropertyChanged ("<#=prop.Name#>Label");
126 | <#
127 | }
128 | #>
129 | }
130 |
131 | <#
132 | if (prop.LabelValue.Length > 0)
133 | {
134 | #>
135 | public string <#=prop.Name#>Label => <#=prop.LabelValue#>;
136 |
137 | <#
138 | }
139 | #>
140 | public <#=prop.Type#> <#=prop.Name#>
141 | {
142 | get { return _<#=prop.Name#>; }
143 | set
144 | {
145 | if (_<#=prop.Name#> == value)
146 | {
147 | return;
148 | }
149 |
150 | var prev = _<#=prop.Name#>;
151 |
152 | _<#=prop.Name#> = value;
153 |
154 | Changed_<#=prop.Name#> (prev, _<#=prop.Name#>);
155 |
156 | Raise_<#=prop.Name#> ();
157 | }
158 | }
159 | // --------------------------------------------------------------------
160 | partial void Changed_<#=prop.Name#> (<#=prop.Type#> prev, <#=prop.Type#> current);
161 | // --------------------------------------------------------------------
162 | // END_PROPERTY: <#=prop.Name#> (<#=prop.Type#>)
163 | // --------------------------------------------------------------------
164 |
165 | <#
166 | }
167 | }
168 | #>
169 |
170 | <#
171 | foreach (var cmd in commands)
172 | {
173 | #>
174 | // --------------------------------------------------------------------
175 | // BEGIN_COMMAND: <#=cmd.Name#>
176 | // --------------------------------------------------------------------
177 | readonly UserCommand _<#=cmd.Name#>;
178 |
179 | bool CanExecute<#=cmd.Name#> ()
180 | {
181 | bool result = false;
182 | CanExecute_<#=cmd.Name#> (ref result);
183 |
184 | return result;
185 | }
186 |
187 | void Execute<#=cmd.Name#> ()
188 | {
189 | Execute_<#=cmd.Name#> ();
190 | }
191 |
192 | public ICommand <#=cmd.Name#> { get { return _<#=cmd.Name#>;} }
193 | // --------------------------------------------------------------------
194 | partial void CanExecute_<#=cmd.Name#> (ref bool result);
195 | partial void Execute_<#=cmd.Name#> ();
196 | // --------------------------------------------------------------------
197 | // END_COMMAND: <#=cmd.Name#>
198 | // --------------------------------------------------------------------
199 |
200 | <#
201 | }
202 | #>
203 |
204 | partial void Constructed ();
205 |
206 | public MainWindowViewModel (Dispatcher dispatcher)
207 | {
208 | _dispatcher = dispatcher;
209 | <#
210 | foreach (var cmd in commands)
211 | {
212 | #>
213 | _<#=cmd.Name#> = new UserCommand (CanExecute<#=cmd.Name#>, Execute<#=cmd.Name#>);
214 | <#
215 | }
216 | #>
217 |
218 | Constructed ();
219 | }
220 |
221 | void ResetCanExecute ()
222 | {
223 | <#
224 | foreach (var cmd in commands)
225 | {
226 | #>
227 | _<#=cmd.Name#>.RefreshCanExecute ();
228 | <#
229 | }
230 | #>
231 | }
232 |
233 | void Dispatch(Action action)
234 | {
235 | _dispatcher.BeginInvoke(action);
236 | }
237 |
238 | protected virtual void OnPropertyChanged (string propertyChanged)
239 | {
240 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyChanged));
241 | }
242 | }
243 | }
244 |
245 | <#+
246 | class PropertyInfo
247 | {
248 | public readonly string Type ;
249 | public readonly string Name ;
250 | public readonly string DefaultValue ;
251 | public readonly string LabelValue ;
252 | public readonly bool IsCollection ;
253 |
254 | public PropertyInfo (string type, string name, string defaultValue, string labelValue, bool isCollection)
255 | {
256 | Type = type ?? "";
257 | Name = name ?? "";
258 | DefaultValue = defaultValue ?? $"default";
259 | LabelValue = labelValue ?? "" ;
260 | IsCollection = isCollection ;
261 | }
262 | }
263 |
264 | static PropertyInfo P (string type, string name, string defaultValue = null) =>
265 | new PropertyInfo (type, name, defaultValue, null, false);
266 |
267 | static PropertyInfo C (string type, string name) =>
268 | new PropertyInfo (type, name, null, null, true);
269 |
270 | static PropertyInfo LP (string type, string name, string labelValue, string defaultValue = null) =>
271 | new PropertyInfo (type, name, defaultValue, labelValue, false);
272 |
273 | static PropertyInfo LC (string type, string name, string labelValue) =>
274 | new PropertyInfo (type, name, null, labelValue, true);
275 |
276 | class CommandInfo
277 | {
278 | public readonly string Name ;
279 |
280 | public CommandInfo (string name)
281 | {
282 | Name = name ?? "";
283 | }
284 | }
285 |
286 | static CommandInfo C (string name) =>
287 | new CommandInfo (name);
288 |
289 | #>
290 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/Header.ttinclude:
--------------------------------------------------------------------------------
1 | // ############################################################################
2 | // # #
3 | // # ---==> T H I S F I L E I S G E N E R A T E D <==--- #
4 | // # #
5 | // # This means that any edits to the .cs file will be lost when its #
6 | // # regenerated. Changes should instead be applied to the corresponding #
7 | // # template file (.tt) #
8 | // ############################################################################
9 |
10 | <#
11 | // ----------------------------------------------------------------------------------------------
12 | // Copyright (c) Mårten Rånge.
13 | // ----------------------------------------------------------------------------------------------
14 | // This source code is subject to terms and conditions of the Microsoft Public License. A
15 | // copy of the license can be found in the License.html file at the root of this distribution.
16 | // If you cannot locate the Microsoft Public License, please send an email to
17 | // dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
18 | // by the terms of the Microsoft Public License.
19 | // ----------------------------------------------------------------------------------------------
20 | // You must not remove this notice, or any other, from this software.
21 | // ----------------------------------------------------------------------------------------------
22 | #>
23 |
24 | <#@ output extension = ".cs" #>
25 | <#@ include file = "HeaderCommon.ttinclude" #>
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/HeaderCommon.ttinclude:
--------------------------------------------------------------------------------
1 | <#
2 | // ----------------------------------------------------------------------------------------------
3 | // Copyright (c) Mårten Rånge.
4 | // ----------------------------------------------------------------------------------------------
5 | // This source code is subject to terms and conditions of the Microsoft Public License. A
6 | // copy of the license can be found in the License.html file at the root of this distribution.
7 | // If you cannot locate the Microsoft Public License, please send an email to
8 | // dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
9 | // by the terms of the Microsoft Public License.
10 | // ----------------------------------------------------------------------------------------------
11 | // You must not remove this notice, or any other, from this software.
12 | // ----------------------------------------------------------------------------------------------
13 | #>
14 |
15 | <#@ assembly name ="System.Core" #>
16 | <#@ import namespace ="System.Collections.Generic" #>
17 | <#@ import namespace ="System.Linq" #>
18 |
19 | <#+
20 | const string S_NoName = "";
21 | const string S_NoType = "";
22 |
23 | static string LeftJustify (string value, int width)
24 | {
25 | value = value ?? "";
26 | return value + new string (' ', Math.Max (0, width - value.Length));
27 | }
28 |
29 | static string RightJustify (string value, int width)
30 | {
31 | value = value ?? "";
32 | return new string (' ', Math.Max (0, width - value.Length)) + value;
33 | }
34 |
35 | sealed class Root
36 | {
37 | }
38 |
39 | interface IBaseEntity
40 | {
41 | void SetParent (object parent);
42 | }
43 |
44 | abstract class BaseEntity : IBaseEntity
45 | where TParent : class
46 | {
47 | public TParent Parent;
48 |
49 | public void SetParent (object parent)
50 | {
51 | Parent = parent as TParent;
52 | }
53 |
54 | }
55 |
56 | abstract class BaseContainer : BaseEntity, IEnumerable
57 | where TContained : class
58 | where TParent : class
59 | {
60 | readonly List m_contained = new List ();
61 |
62 | public void Add (TContained contained)
63 | {
64 | if (contained != null)
65 | {
66 | var be = contained as IBaseEntity;
67 | if (be != null)
68 | {
69 | be.SetParent (this);
70 | }
71 | m_contained.Add (contained);
72 | }
73 | }
74 |
75 | public void Replace (IEnumerable range)
76 | {
77 | range = range ?? new TContained[0];
78 |
79 | foreach (var be in range.OfType())
80 | {
81 | be.SetParent (this);
82 | }
83 |
84 | m_contained.Clear ();
85 | m_contained.AddRange (range);
86 | }
87 |
88 | public IEnumerator GetEnumerator ()
89 | {
90 | return m_contained.GetEnumerator ();
91 | }
92 |
93 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
94 | {
95 | return m_contained.GetEnumerator ();
96 | }
97 |
98 |
99 | }
100 | #>
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/HexFileLoader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 |
5 | namespace Essenbee.Z80.Debugger
6 | {
7 | public static class HexFileLoader
8 | {
9 | public static (byte[], ushort, ushort) Read(string filePath, byte[] RAM)
10 | {
11 | var lines = File.ReadAllLines(filePath);
12 | ushort initialAddr = 0;
13 | ushort endAddr = 0;
14 | var lineNo = 0;
15 |
16 | foreach (var line in lines)
17 | {
18 | if (line.Equals(":00000001FF", StringComparison.InvariantCultureIgnoreCase))
19 | {
20 | break;
21 | }
22 |
23 | lineNo++;
24 |
25 | var dataLength = Convert.ToInt32(line[1..3], 16);
26 | var startAddr = (ushort)Convert.ToInt32(line[3..7], 16);
27 | var recType = line[7..9];
28 |
29 | if (recType == "00")
30 | {
31 | if (lineNo == 1) initialAddr = startAddr;
32 |
33 | // Data record
34 | var dataEnd = (2 * dataLength) + 9;
35 | var data = line[9..dataEnd];
36 | var dataBytes = new List();
37 |
38 | for (int i = 0; i < dataLength * 2; i++)
39 | {
40 | if (i == 0 || i % 2 == 0)
41 | {
42 | dataBytes.Add((byte)Convert.ToInt32(data.Substring(i, 2), 16));
43 | }
44 | }
45 |
46 | foreach (var datum in dataBytes)
47 | {
48 | RAM[startAddr++] = datum;
49 | }
50 | }
51 |
52 | endAddr = startAddr;
53 | }
54 |
55 | if (endAddr > RAM.Length)
56 | {
57 | endAddr = (ushort)RAM.Length;
58 | }
59 |
60 | return (RAM, initialAddr, endAddr);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
54 |
55 |
56 |
57 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace Essenbee.Z80.Debugger
17 | {
18 | ///
19 | /// Interaction logic for MainWindow.xaml
20 | ///
21 | public partial class MainWindow : Window
22 | {
23 | private readonly MainWindowViewModel _viewModel;
24 |
25 | public MainWindow()
26 | {
27 | InitializeComponent();
28 | _viewModel = new MainWindowViewModel(Dispatcher);
29 | DataContext = _viewModel;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.IO;
5 | using System.Linq;
6 | using static Essenbee.Z80.Z80;
7 |
8 | namespace Essenbee.Z80.Debugger
9 | {
10 | public partial class MainWindowViewModel
11 | {
12 | private Z80 _cpu;
13 | private IBus _basicBus;
14 | private ushort _disassembleFrom;
15 | private ushort _disassembleTo;
16 |
17 | // ================== Construction Event ==================
18 | partial void Constructed()
19 | {
20 | _basicBus = new BasicBus(64);
21 | RawMemory = _basicBus.RAM;
22 | _cpu = new Z80 { PC = 0x8000 }; //Default start location
23 | _cpu.ConnectToBus(_basicBus);
24 | ProgramCounter = _cpu.PC.ToString("X4");
25 | StackPointer = _cpu.SP.ToString("X4");
26 | IndexX = _cpu.IX.ToString("X4");
27 | IndexY = _cpu.IY.ToString("X4");
28 | InterruptVector = _cpu.I.ToString("X2");
29 | Refresh = _cpu.R.ToString("X2");
30 | QRegister = ((byte)_cpu.Q).ToString("X2");
31 | MemPointer = _cpu.MEMPTR.ToString("X4");
32 | SetRegisterPairs();
33 | SetFlags();
34 | Mode = GetInterruptMode();
35 |
36 | Memory = BuildMemoryMap();
37 | MemoryMapRow = GetMemoryMapRow(_cpu.PC);
38 | }
39 |
40 | // ================== Property Events ==================
41 | partial void Changed_ProgramCounter(string prev, string current)
42 | {
43 | _cpu.PC = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber) ;
44 | }
45 |
46 | partial void Changed_AccuFlags(string prev, string current)
47 | {
48 | var temp = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber);
49 | _cpu.A = (byte)((temp & 0xFF00) >> 8);
50 | _cpu.F = (Flags)(temp & 0x00FF);
51 | }
52 |
53 | partial void Changed_AccuFlagsPrime(string prev, string current)
54 | {
55 | var temp = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber);
56 | _cpu.A1 = (byte)((temp & 0xFF00) >> 8);
57 | _cpu.F1 = (Flags)(temp & 0x00FF);
58 | }
59 |
60 | partial void Changed_HLPair(string prev, string current)
61 | {
62 | var temp = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber);
63 | _cpu.H = (byte)((temp & 0xFF00) >> 8);
64 | _cpu.L = (byte)(temp & 0x00FF);
65 | }
66 |
67 | partial void Changed_HLPairPrime(string prev, string current)
68 | {
69 | var temp = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber);
70 | _cpu.H1 = (byte)((temp & 0xFF00) >> 8);
71 | _cpu.L1 = (byte)(temp & 0x00FF);
72 | }
73 |
74 | partial void Changed_BCPair(string prev, string current)
75 | {
76 | var temp = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber);
77 | _cpu.B = (byte)((temp & 0xFF00) >> 8);
78 | _cpu.C = (byte)(temp & 0x00FF);
79 | }
80 |
81 | partial void Changed_BCPairPrime(string prev, string current)
82 | {
83 | var temp = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber);
84 | _cpu.B1 = (byte)((temp & 0xFF00) >> 8);
85 | _cpu.C1 = (byte)(temp & 0x00FF);
86 | }
87 |
88 | partial void Changed_DEPair(string prev, string current)
89 | {
90 | var temp = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber);
91 | _cpu.D = (byte)((temp & 0xFF00) >> 8);
92 | _cpu.E = (byte)(temp & 0x00FF);
93 | }
94 |
95 | partial void Changed_DEPairPrime(string prev, string current)
96 | {
97 | var temp = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber);
98 | _cpu.D1 = (byte)((temp & 0xFF00) >> 8);
99 | _cpu.E1 = (byte)(temp & 0x00FF);
100 | }
101 |
102 | partial void Changed_StackPointer(string prev, string current)
103 | {
104 | _cpu.SP = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber);
105 | MemoryMapRow = GetMemoryMapRow(_cpu.PC);
106 | }
107 |
108 | partial void Changed_IndexX(string prev, string current)
109 | {
110 | _cpu.IX = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber);
111 | }
112 |
113 | partial void Changed_IndexY(string prev, string current)
114 | {
115 | _cpu.IY = ushort.Parse(current, System.Globalization.NumberStyles.HexNumber);
116 | }
117 |
118 | partial void Changed_InterruptVector(string prev, string current)
119 | {
120 | _cpu.I = byte.Parse(current, System.Globalization.NumberStyles.HexNumber);
121 | }
122 |
123 | partial void Changed_Refresh(string prev, string current)
124 | {
125 | _cpu.R = byte.Parse(current, System.Globalization.NumberStyles.HexNumber);
126 | }
127 |
128 | partial void Changed_SignBit(bool prev, bool current)
129 | {
130 | SetFlag(Flags.S, current);
131 | }
132 |
133 | partial void Changed_ZeroBit(bool prev, bool current)
134 | {
135 | SetFlag(Flags.Z, current);
136 | }
137 |
138 | partial void Changed_UBit(bool prev, bool current)
139 | {
140 | SetFlag(Flags.U, current);
141 | }
142 |
143 | partial void Changed_HalfCarryBit(bool prev, bool current)
144 | {
145 | SetFlag(Flags.H, current);
146 | }
147 |
148 | partial void Changed_XBit(bool prev, bool current)
149 | {
150 | SetFlag(Flags.X, current);
151 | }
152 |
153 | partial void Changed_ParityOverflowBit(bool prev, bool current)
154 | {
155 | SetFlag(Flags.P, current);
156 | }
157 |
158 | partial void Changed_NegationBit(bool prev, bool current)
159 | {
160 | SetFlag(Flags.N, current);
161 | }
162 |
163 | partial void Changed_CarryBit(bool prev, bool current)
164 | {
165 | SetFlag(Flags.C, current);
166 | }
167 |
168 | // ================== Command Events ==================
169 | partial void CanExecute_StepCommand(ref bool result)
170 | {
171 | result = _cpu != null;
172 | }
173 |
174 | partial void Execute_StepCommand()
175 | {
176 | _cpu.Step();
177 | SelectedRow = _cpu.PC.ToString("X4");
178 | Memory = BuildMemoryMap();
179 | ProgramCounter = _cpu.PC.ToString("X4");
180 | MemoryMapRow = GetMemoryMapRow(_cpu.PC);
181 | StackPointer = _cpu.SP.ToString("X4");
182 | IndexX = _cpu.IX.ToString("X4");
183 | IndexY = _cpu.IY.ToString("X4");
184 | InterruptVector = _cpu.I.ToString("X2");
185 | Refresh = _cpu.R.ToString("X2");
186 | QRegister = ((byte)_cpu.Q).ToString("X2");
187 | MemPointer = _cpu.MEMPTR.ToString("X4");
188 | Mode = GetInterruptMode();
189 | SetRegisterPairs();
190 | SetFlags();
191 | }
192 |
193 | partial void CanExecute_LoadCommand(ref bool result)
194 | {
195 | result = _cpu != null;
196 | }
197 |
198 | partial void Execute_LoadCommand()
199 | {
200 | var openFileDialog = new Microsoft.Win32.OpenFileDialog()
201 | {
202 | Filter = "HEX file (*.hex)|*.hex"
203 | };
204 |
205 | var result = openFileDialog.ShowDialog();
206 |
207 | if (result ?? false)
208 | {
209 | var fileName = openFileDialog.FileName;
210 | var (RAM, startAddr, endAddr) = HexFileLoader.Read(fileName, new byte[64 * 1024]);
211 | _basicBus = new BasicBus(RAM);
212 | RawMemory = _basicBus.RAM;
213 | _cpu.ConnectToBus(_basicBus);
214 | Memory = BuildMemoryMap();
215 | _cpu.PC = startAddr;
216 | ProgramCounter = _cpu.PC.ToString("X4");
217 | MemoryMapRow = GetMemoryMapRow(startAddr);
218 | var disassembly = _cpu.Disassemble(startAddr, endAddr);
219 | DisAsm = GetDisassembedProgram(disassembly);
220 | SelectedRow = startAddr.ToString("X4");
221 | }
222 | }
223 |
224 | partial void CanExecute_LoadRomCommand(ref bool result)
225 | {
226 | result = _cpu != null;
227 | }
228 |
229 | partial void Execute_LoadRomCommand()
230 | {
231 | ushort startAddr = 0x000;
232 |
233 | var openFileDialog = new Microsoft.Win32.OpenFileDialog()
234 | {
235 | Filter = "ROM file (*.rom)|*.rom"
236 | };
237 |
238 | var result = openFileDialog.ShowDialog();
239 |
240 | if (result ?? false)
241 | {
242 | var RAM = new byte[65536];
243 | Array.Clear(RAM, 0, RAM.Length);
244 | var fileName = openFileDialog.FileName;
245 | var rom = File.ReadAllBytes(fileName);
246 | var dataBlocks = new List<(ushort, ushort)>();
247 |
248 | if (File.Exists($"{fileName}.data"))
249 | {
250 | var romData = File.ReadAllLines($"{fileName}.data");
251 |
252 | foreach (var line in romData)
253 | {
254 | var address = line.Split(',');
255 | dataBlocks.Add((Convert.ToUInt16(address[0], 16), Convert.ToUInt16(address[1], 16)));
256 | }
257 | }
258 |
259 | var endAddr = (ushort)rom.Length;
260 | Array.Copy(rom, RAM, rom.Length);
261 |
262 | _basicBus = new BasicBus(RAM);
263 | RawMemory = _basicBus.RAM;
264 | _cpu.ConnectToBus(_basicBus);
265 | Memory = BuildMemoryMap();
266 | _cpu.PC = startAddr;
267 | ProgramCounter = _cpu.PC.ToString("X4");
268 |
269 | MemoryMapRow = GetMemoryMapRow(startAddr);
270 | var disassembly = _cpu.Disassemble(startAddr, endAddr, dataBlocks);
271 | DisAsm = GetDisassembedProgram(disassembly);
272 |
273 | SelectedRow = startAddr.ToString("X4");
274 | }
275 | }
276 |
277 | private Dictionary GetDisassembedProgram(Dictionary disassembly)
278 | {
279 | var retVal = new Dictionary();
280 |
281 | foreach (var line in disassembly)
282 | {
283 | var addr = line.Key.ToString("X4");
284 | retVal.Add(addr, line.Value);
285 | }
286 |
287 | return retVal;
288 | }
289 |
290 | private Dictionary BuildMemoryMap()
291 | {
292 | var memory = _basicBus.RAM.Select((a, b) => new { a, b })
293 | .ToDictionary(mem => mem.b, mem => mem.a);
294 | var memoryMap = new Dictionary();
295 | for (int i = 0; i < memory.Count; i += 16)
296 | {
297 | memoryMap[i.ToString("X4")] = memory[i].ToString("X2") + " ";
298 |
299 | for (int x = 1; x < 15; x++)
300 | {
301 | memoryMap[i.ToString("X4")] += memory[i + x].ToString("X2") + " ";
302 | }
303 |
304 | memoryMap[i.ToString("X4")] += memory[i + 15].ToString("X2");
305 | }
306 |
307 | return memoryMap;
308 | }
309 |
310 | private int GetMemoryMapRow(int programCounter)
311 | {
312 | var row = programCounter / 16;
313 | return row;
314 | }
315 |
316 | private void SetFlags()
317 | {
318 | SignBit = CheckFlag(Flags.S);
319 | ZeroBit = CheckFlag(Flags.Z);
320 | UBit = CheckFlag(Flags.U);
321 | HalfCarryBit = CheckFlag(Flags.H);
322 | XBit = CheckFlag(Flags.X);
323 | ParityOverflowBit = CheckFlag(Flags.P);
324 | NegationBit = CheckFlag(Flags.N);
325 | CarryBit = CheckFlag(Flags.C);
326 | }
327 |
328 | private void SetRegisterPairs()
329 | {
330 | AccuFlags = _cpu.AF.ToString("X4");
331 | AccuFlagsPrime = _cpu.AF1.ToString("X4");
332 | HLPair = _cpu.HL.ToString("X4");
333 | HLPairPrime = _cpu.HL1.ToString("X4");
334 | BCPair = _cpu.BC.ToString("X4");
335 | BCPairPrime = _cpu.BC1.ToString("X4");
336 | DEPair = _cpu.DE.ToString("X4");
337 | DEPairPrime = _cpu.DE1.ToString("X4");
338 | }
339 |
340 | private int GetInterruptMode() => _cpu.InterruptMode switch
341 | {
342 | InterruptMode.Mode0 => 0,
343 | InterruptMode.Mode1 => 1,
344 | InterruptMode.Mode2 => 2,
345 | _ => 0
346 | };
347 |
348 | private bool CheckFlag(Flags flag)
349 | {
350 | if ((_cpu.F & flag) == flag)
351 | {
352 | return true;
353 | }
354 |
355 | return false;
356 | }
357 |
358 | private void SetFlag(Flags flag, bool value)
359 | {
360 | if (value)
361 | {
362 | _cpu.F |= flag;
363 | }
364 | else
365 | {
366 | _cpu.F &= ~flag;
367 | }
368 | }
369 | }
370 | }
371 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/MemoryMap.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
21 |
22 |
29 |
30 |
31 |
36 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/MemoryMap.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Data;
7 | using System.Windows.Documents;
8 | using System.Windows.Input;
9 | using System.Windows.Media;
10 | using System.Windows.Media.Imaging;
11 | using System.Windows.Navigation;
12 | using System.Windows.Shapes;
13 |
14 | namespace Essenbee.Z80.Debugger
15 | {
16 | ///
17 | /// Interaction logic for MemoryMap.xaml
18 | ///
19 | public partial class MemoryMap : UserControl
20 | {
21 | public MemoryMap()
22 | {
23 | InitializeComponent();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/MsxScreen.xaml:
--------------------------------------------------------------------------------
1 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/MsxScreen.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using System.Windows.Media;
6 | using System.Windows.Media.Imaging;
7 | using System.Windows.Threading;
8 |
9 | namespace Essenbee.Z80.Debugger
10 | {
11 | public partial class MsxScreen : UserControl
12 | {
13 | const int screen = 0x4000;
14 | const int width = 256;
15 | const int height = 192;
16 | const int stride = 3;
17 | DispatcherTimer _refreshTimer;
18 | WriteableBitmap _screenBitmap;
19 | byte[] _screenPixels;
20 |
21 | partial void Constructed__MsxScreen()
22 | {
23 | InitializeComponent();
24 | _refreshTimer = new DispatcherTimer(
25 | TimeSpan.FromMilliseconds(200)
26 | , DispatcherPriority.ApplicationIdle
27 | , OnRefresh
28 | , Dispatcher
29 | );
30 | _screenBitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr24, null);
31 | _screenPixels = new byte[width*height*stride];
32 | img.Source = _screenBitmap;
33 |
34 | _refreshTimer.Start();
35 | }
36 |
37 | void OnRefresh(object sender, EventArgs e)
38 | {
39 | // NOOOOOOO!!!!! I don't want to go through linq to get the array :(
40 | var memory = RawMemory?.ToArray();
41 |
42 | if (memory != null && memory.Length >= screen + 0x1800)
43 | {
44 | // The layout of MSX graphics memory is a bit weird
45 | for (var s = 0; s < 3; ++s)
46 | {
47 | for (var cl = 0; cl < 8; ++cl)
48 | {
49 | for (var l = 0; l < 8; ++l)
50 | {
51 | var memOffset = screen + (s << 11) + (cl << 8) + (l << 5);
52 | for (var cx = 0; cx < 32; ++cx)
53 | {
54 | var c = memory[memOffset + cx];
55 | var bitmapOffset = stride*(((s << 11) + (l << 8) + (cl << 5) + cx) << 3);
56 | for (int x = 0, strideX = 0; x < 8; ++x, strideX += stride)
57 | {
58 | var b = 0x1 & (c >> (7 - x));
59 | // TODO: Add support for attribute area
60 | if (b != 0)
61 | {
62 | _screenPixels[bitmapOffset + strideX + 0] = 0xFF;
63 | _screenPixels[bitmapOffset + strideX + 1] = 0xFF;
64 | _screenPixels[bitmapOffset + strideX + 2] = 0xFF;
65 | }
66 | else
67 | {
68 | _screenPixels[bitmapOffset + strideX + 0] = 0x00;
69 | _screenPixels[bitmapOffset + strideX + 1] = 0x00;
70 | _screenPixels[bitmapOffset + strideX + 2] = 0x00;
71 | }
72 | }
73 | }
74 | }
75 | }
76 | }
77 | }
78 | _screenBitmap.WritePixels(new Int32Rect(0, 0, width, height), _screenPixels, width*stride, 0);
79 | }
80 |
81 |
82 |
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/ROM/48.rom:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/essenbee/z80emu/8085dc449deb35d8b76ed76e26c4b1347e1286eb/Essenbee.Z80.Debugger/ROM/48.rom
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/ROM/48.rom.data:
--------------------------------------------------------------------------------
1 | 0205,028D,Key Tables
2 | 048E,04A5,Semi-tone Table
3 | 0A11,0A22,Control Character Table
4 | 0FA0,0FA8,Editing Keys Table
5 | 15AF,15C3,Initial Channel Info
6 | 15C6,15D3,Initial Stream Data
7 | 162D,1633,Channel Code Lookup
8 | 1716,171A,Close Streams Lookup
9 | 177A,1780,Open Streams Lookup
10 | 1A48,1B16,Syntax Tables
11 | 1C01,1C0C,Command Class Table
12 | 2596,25AE,Scanning Function Table
13 | 2795,27AF,Operators
14 | 27B0,27BC,Priorities
15 | 3265,32D2,Constants
16 | 32D7,335A,Addresses
17 | 3D00,3FFF,Character Set
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/StackDisplay.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
19 |
20 |
25 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/StackDisplay.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Data;
7 | using System.Windows.Documents;
8 | using System.Windows.Input;
9 | using System.Windows.Media;
10 | using System.Windows.Media.Imaging;
11 | using System.Windows.Navigation;
12 | using System.Windows.Shapes;
13 |
14 | namespace Essenbee.Z80.Debugger
15 | {
16 | ///
17 | /// Interaction logic for StackDisplay.xaml
18 | ///
19 | public partial class StackDisplay : UserControl
20 | {
21 | public StackDisplay()
22 | {
23 | InitializeComponent();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Debugger/UserCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Input;
3 |
4 | namespace Essenbee.Z80.Debugger
5 | {
6 | public class UserCommand : ICommand
7 | {
8 | public event EventHandler CanExecuteChanged;
9 | private readonly Func _canExecute;
10 | private readonly Action _execute;
11 |
12 | public UserCommand(Func canExecute, Action execute)
13 | {
14 | _canExecute = canExecute;
15 | _execute = execute;
16 | }
17 |
18 | public bool CanExecute(object parameter)
19 | {
20 | return _canExecute();
21 | }
22 |
23 | public void Execute(object parameter)
24 | {
25 | _execute();
26 | }
27 |
28 | public void RefreshCanExecute()
29 | {
30 | CanExecuteChanged(this, new EventArgs());
31 | }
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Sample/Essenbee.Z80.Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Sample/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Essenbee.Z80.Sample
5 | {
6 | class Program
7 | {
8 | private static Z80 _cpu;
9 | private static string _rom = @"..\..\..\ROM\48.rom";
10 |
11 | static void Main(string[] args)
12 | {
13 | var ram = new byte[65536];
14 | Array.Clear(ram, 0, ram.Length);
15 | var romData = File.ReadAllBytes(_rom);
16 |
17 | if (romData.Length != 16384)
18 | {
19 | throw new InvalidOperationException("Not a valid ROM file");
20 | }
21 |
22 | Array.Copy(romData, ram, 16384);
23 | _cpu = new Z80();
24 | IBus simpleBus = new SimpleBus(ram);
25 | _cpu.ConnectToBus(simpleBus);
26 |
27 | Console.Clear();
28 |
29 | while (!(Console.KeyAvailable && (Console.ReadKey(true).Key == ConsoleKey.Escape)))
30 | {
31 | try
32 | {
33 | _cpu.Step();
34 | Console.Write($"\rPC: {_cpu.PC.ToString("X4")}");
35 | }
36 | catch (Exception ex)
37 | {
38 | Console.WriteLine(ex.Message);
39 | Console.WriteLine(ex.StackTrace);
40 | Console.ReadLine();
41 | }
42 | }
43 |
44 | Console.WriteLine();
45 | Console.WriteLine();
46 |
47 | for (var i = 0x4000; i < 0x5800; i++)
48 | {
49 | if (i % 16 == 0) Console.Write("{0:X4} | ", i);
50 | {
51 | Console.Write("{0:x2} ", ram[i]);
52 | }
53 |
54 | if (i % 8 == 7)
55 | {
56 | Console.Write(" ");
57 | }
58 |
59 | if (i % 16 == 15)
60 | {
61 | Console.WriteLine();
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Sample/ROM/48.rom:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/essenbee/z80emu/8085dc449deb35d8b76ed76e26c4b1347e1286eb/Essenbee.Z80.Sample/ROM/48.rom
--------------------------------------------------------------------------------
/Essenbee.Z80.Sample/SimpleBus.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Essenbee.Z80.Sample
5 | {
6 | public class SimpleBus : IBus
7 | {
8 | public bool Interrupt { get; set; }
9 | public bool NonMaskableInterrupt { get; set; }
10 | public IList Data { get; set; } = new List();
11 |
12 | private byte[] _memory;
13 |
14 | public SimpleBus(byte[] ram)
15 | {
16 | _memory = ram;
17 | }
18 |
19 | public IReadOnlyCollection RAM
20 | {
21 | get => _memory;
22 | }
23 |
24 | public byte Read(ushort addr, bool ro = false)
25 | {
26 | return _memory[addr];
27 | }
28 |
29 | public byte ReadPeripheral(ushort port)
30 | {
31 | Console.WriteLine();
32 | Console.WriteLine($"IN 0x{port:X4}");
33 |
34 | return 0;
35 | }
36 |
37 | public void Write(ushort addr, byte data)
38 | {
39 | _memory[addr] = data;
40 | }
41 |
42 | public void WritePeripheral(ushort port, byte data)
43 | {
44 | Console.WriteLine();
45 | Console.WriteLine($"OUT 0x{port:X4}, 0x{data:X2}");
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/BinaryCodedDecimalArithmeticTests.cs:
--------------------------------------------------------------------------------
1 | using FakeItEasy;
2 | using System.Collections.Generic;
3 | using Xunit;
4 |
5 | namespace Essenbee.Z80.Tests
6 | {
7 | public class BinaryCodedDecimalArithmeticTests
8 | {
9 | [Fact]
10 | private void EightBitBCDAddition()
11 | {
12 | var fakeBus = A.Fake();
13 |
14 | var program = new Dictionary
15 | {
16 | // Program Code
17 | { 0x0080, 0x3E }, // LD A,15h 0001 0101
18 | { 0x0081, 0x15 },
19 | { 0x0082, 0x06 }, // LD B,27h 0010 0111
20 | { 0x0083, 0x27 },
21 | { 0x0084, 0x80 }, // ADD A,B
22 | { 0x0085, 0x27 }, // DAA
23 | { 0x0086, 0x00 },
24 | { 0x0087, 0x00 },
25 | { 0x0088, 0x00 },
26 | };
27 |
28 | A.CallTo(() => fakeBus.Read(A._, A._))
29 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
30 |
31 | var cpu = new Z80() { A = 0x00, PC = 0x0080 };
32 | cpu.ConnectToBus(fakeBus);
33 |
34 | for (int i = 0; i < 5; i++)
35 | {
36 | cpu.Step();
37 | }
38 |
39 | // BCD 42 is the answer... 0100 0010
40 | Assert.Equal(0x04, cpu.A >> 4); // Tens digit is 4
41 | Assert.Equal(0x02, cpu.A & 0x0F); // Ones digit is 2
42 |
43 | Assert.False((cpu.F & Z80.Flags.C) == Z80.Flags.C);
44 | Assert.False((cpu.F & Z80.Flags.N) == Z80.Flags.N);
45 | Assert.True((cpu.F & Z80.Flags.P) == Z80.Flags.P); // Even parity
46 | Assert.False((cpu.F & Z80.Flags.X) == Z80.Flags.X);
47 | Assert.True((cpu.F & Z80.Flags.H) == Z80.Flags.H);
48 | Assert.False((cpu.F & Z80.Flags.U) == Z80.Flags.U);
49 | Assert.False((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
50 | Assert.False((cpu.F & Z80.Flags.S) == Z80.Flags.S);
51 | }
52 |
53 | [Fact]
54 | private void EightBitBCDSubtractionNegativeResult()
55 | {
56 | var fakeBus = A.Fake();
57 |
58 | var program = new Dictionary
59 | {
60 | // Program Code
61 | { 0x0080, 0x3E }, // LD A,15h 0001 0101
62 | { 0x0081, 0x15 },
63 | { 0x0082, 0x06 }, // LD B,27h 0010 0111
64 | { 0x0083, 0x27 },
65 | { 0x0084, 0x90 }, // SUB A,B
66 | { 0x0085, 0x27 }, // DAA
67 | { 0x0086, 0x00 },
68 | { 0x0087, 0x00 },
69 | { 0x0088, 0x00 },
70 | };
71 |
72 | A.CallTo(() => fakeBus.Read(A._, A._))
73 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
74 |
75 | var cpu = new Z80() { A = 0x00, PC = 0x0080 };
76 | cpu.ConnectToBus(fakeBus);
77 |
78 | for (int i = 0; i < 5; i++)
79 | {
80 | cpu.Step();
81 | }
82 |
83 | // BCD -12 is the answer (represented as the 10s complement form, 88) with sign bit set
84 | Assert.Equal(0x08, cpu.A >> 4);
85 | Assert.Equal(0x08, cpu.A & 0x0F);
86 |
87 | // Reverse the tens complement form...
88 | Assert.True((cpu.F & Z80.Flags.S) == Z80.Flags.S); // Sign is -
89 | Assert.Equal(0x01, 9 - (cpu.A >> 4)); // Tens digit is 1
90 | Assert.Equal(0x02, 9 - (cpu.A & 0x0F) + 1); // Ones digit is 2
91 |
92 | Assert.True((cpu.F & Z80.Flags.C) == Z80.Flags.C);
93 | Assert.True((cpu.F & Z80.Flags.N) == Z80.Flags.N); // Subtraction
94 | Assert.True((cpu.F & Z80.Flags.P) == Z80.Flags.P); // Even parity
95 | Assert.True((cpu.F & Z80.Flags.X) == Z80.Flags.X);
96 | Assert.False((cpu.F & Z80.Flags.H) == Z80.Flags.H);
97 | Assert.False((cpu.F & Z80.Flags.U) == Z80.Flags.U);
98 | Assert.False((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
99 | }
100 |
101 | [Fact]
102 | private void EightBitBCDSubtraction()
103 | {
104 | var fakeBus = A.Fake();
105 |
106 | var program = new Dictionary
107 | {
108 | // Program Code
109 | { 0x0080, 0x3E }, // LD A,27h 0010 0111
110 | { 0x0081, 0x27 },
111 | { 0x0082, 0x06 }, // LD B,15h 0001 0101
112 | { 0x0083, 0x15 },
113 | { 0x0084, 0x90 }, // SUB A,B
114 | { 0x0085, 0x27 }, // DAA
115 | { 0x0086, 0x00 },
116 | { 0x0087, 0x00 },
117 | { 0x0088, 0x00 },
118 | };
119 |
120 | A.CallTo(() => fakeBus.Read(A._, A._))
121 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
122 |
123 | var cpu = new Z80() { A = 0x00, PC = 0x0080 };
124 | cpu.ConnectToBus(fakeBus);
125 |
126 | for (int i = 0; i < 5; i++)
127 | {
128 | cpu.Step();
129 | }
130 |
131 | // BCD 12 is the answer... 00001 0010
132 | Assert.Equal(0x01, cpu.A >> 4); // Tens digit is 1
133 | Assert.Equal(0x02, cpu.A & 0x0F); // Ones digit is 2
134 |
135 | Assert.False((cpu.F & Z80.Flags.C) == Z80.Flags.C);
136 | Assert.True((cpu.F & Z80.Flags.N) == Z80.Flags.N); // Subtraction
137 | Assert.True((cpu.F & Z80.Flags.P) == Z80.Flags.P); // Even parity
138 | Assert.False((cpu.F & Z80.Flags.X) == Z80.Flags.X);
139 | Assert.False((cpu.F & Z80.Flags.H) == Z80.Flags.H);
140 | Assert.False((cpu.F & Z80.Flags.U) == Z80.Flags.U);
141 | Assert.False((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
142 | Assert.False((cpu.F & Z80.Flags.S) == Z80.Flags.S);
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/CallAndReturnGroupShould.cs:
--------------------------------------------------------------------------------
1 | using FakeItEasy;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 | using Xunit;
6 | using static Essenbee.Z80.Z80;
7 |
8 | namespace Essenbee.Z80.Tests
9 | {
10 | public class CallAndReturnGroupShould
11 | {
12 | [Fact]
13 | private void PushAndSetProgramCounterForCALL()
14 | {
15 | var fakeBus = A.Fake();
16 |
17 | var program = new Dictionary
18 | {
19 | // Program Code
20 | { 0x0080, 0xCD }, // CALL &0190
21 | { 0x0081, 0x90 },
22 | { 0x0082, 0x01 },
23 | { 0x0083, 0x00 },
24 | { 0x0084, 0x00 },
25 |
26 | { 0x0190, 0x00 }, // <- Subroutine
27 | { 0x0191, 0x00 },
28 | { 0x0192, 0x00 },
29 |
30 | { 0x1FFB, 0x00 },
31 | { 0x1FFC, 0x00 },
32 | { 0x1FFD, 0x00 },
33 | { 0x1FFE, 0x00 },
34 | { 0x1FFF, 0x00 },
35 | { 0x2000, 0x00 }, // <- SP
36 | };
37 |
38 | A.CallTo(() => fakeBus.Read(A._, A._))
39 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
40 | A.CallTo(() => fakeBus.Write(A._, A._))
41 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
42 |
43 | var cpu = new Z80() { A = 0x00, PC = 0x0080, SP = 0x2000 };
44 | cpu.ConnectToBus(fakeBus);
45 |
46 | cpu.Step();
47 |
48 | Assert.Equal(0x0190, cpu.PC);
49 | Assert.Equal(0x1FFE, cpu.SP);
50 | Assert.Equal(0x00, program[0x1FFF]);
51 | Assert.Equal(0x83, program[0x1FFE]);
52 |
53 | void UpdateMemory(ushort addr, byte data)
54 | {
55 | program[addr] = data;
56 | }
57 | }
58 |
59 | [Fact]
60 | private void PushAndSetProgramCounterForCALLCC_GivenZero()
61 | {
62 | var fakeBus = A.Fake();
63 |
64 | var program = new Dictionary
65 | {
66 | // Program Code
67 | { 0x0080, 0xCD }, // CALL &0190
68 | { 0x0081, 0x90 },
69 | { 0x0082, 0x01 },
70 | { 0x0083, 0x00 },
71 | { 0x0084, 0x00 },
72 |
73 | { 0x0190, 0x00 }, // <- Subroutine
74 | { 0x0191, 0x00 },
75 | { 0x0192, 0x00 },
76 |
77 | { 0x1FFB, 0x00 },
78 | { 0x1FFC, 0x00 },
79 | { 0x1FFD, 0x00 },
80 | { 0x1FFE, 0x00 },
81 | { 0x1FFF, 0x00 },
82 | { 0x2000, 0x00 }, // <- SP
83 | };
84 |
85 | A.CallTo(() => fakeBus.Read(A._, A._))
86 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
87 | A.CallTo(() => fakeBus.Write(A._, A._))
88 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
89 |
90 | var cpu = new Z80() { A = 0x00, PC = 0x0080, SP = 0x2000 };
91 | cpu.ConnectToBus(fakeBus);
92 | cpu.F = (Flags)0b01000000; // Set Z flag
93 |
94 | cpu.Step();
95 |
96 | Assert.Equal(0x0190, cpu.PC);
97 | Assert.Equal(0x1FFE, cpu.SP);
98 | Assert.Equal(0x00, program[0x1FFF]);
99 | Assert.Equal(0x83, program[0x1FFE]);
100 |
101 | void UpdateMemory(ushort addr, byte data)
102 | {
103 | program[addr] = data;
104 | }
105 | }
106 |
107 | [Fact]
108 | private void DoNothingForCALLCC_GivenNotZero()
109 | {
110 | var fakeBus = A.Fake();
111 |
112 | var program = new Dictionary
113 | {
114 | // Program Code
115 | { 0x0080, 0xCC }, // CALL Z, &0190
116 | { 0x0081, 0x90 },
117 | { 0x0082, 0x01 },
118 | { 0x0083, 0x00 },
119 | { 0x0084, 0x00 },
120 |
121 | { 0x0190, 0x00 }, // <- Subroutine
122 | { 0x0191, 0x00 },
123 | { 0x0192, 0x00 },
124 |
125 | { 0x1FFB, 0x00 },
126 | { 0x1FFC, 0x00 },
127 | { 0x1FFD, 0x00 },
128 | { 0x1FFE, 0x00 },
129 | { 0x1FFF, 0x00 },
130 | { 0x2000, 0x00 }, // <- SP
131 | };
132 |
133 | A.CallTo(() => fakeBus.Read(A._, A._))
134 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
135 | A.CallTo(() => fakeBus.Write(A._, A._))
136 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
137 |
138 | var cpu = new Z80() { A = 0x00, PC = 0x0080, SP = 0x2000 };
139 | cpu.ConnectToBus(fakeBus);
140 | cpu.F = (Flags)0b00000000; // Reset Z flag
141 |
142 | cpu.Step();
143 |
144 | Assert.Equal(0x0083, cpu.PC);
145 | Assert.Equal(0x2000, cpu.SP);
146 |
147 | void UpdateMemory(ushort addr, byte data)
148 | {
149 | program[addr] = data;
150 | }
151 | }
152 |
153 | [Fact]
154 | private void PopProgramCounterForRET()
155 | {
156 | var fakeBus = A.Fake();
157 |
158 | var program = new Dictionary
159 | {
160 | // Program Code
161 | { 0x0080, 0xC9 }, // RET
162 | { 0x0081, 0x00 },
163 | { 0x0082, 0x00 },
164 | { 0x0083, 0x00 },
165 | { 0x0084, 0x00 },
166 |
167 | { 0x0190, 0x00 }, // <- Continue from here
168 | { 0x0191, 0x00 },
169 | { 0x0192, 0x00 },
170 |
171 | { 0x1FFB, 0x00 },
172 | { 0x1FFC, 0x00 },
173 | { 0x1FFD, 0x00 },
174 | { 0x1FFE, 0x90 },// <- SP
175 | { 0x1FFF, 0x01 },
176 | { 0x2000, 0x00 },
177 | };
178 |
179 | A.CallTo(() => fakeBus.Read(A._, A._))
180 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
181 | A.CallTo(() => fakeBus.Write(A._, A._))
182 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
183 |
184 | var cpu = new Z80() { A = 0x00, PC = 0x0080, SP = 0x1FFE };
185 | cpu.ConnectToBus(fakeBus);
186 |
187 | cpu.Step();
188 |
189 | Assert.Equal(0x0190, cpu.PC);
190 | Assert.Equal(0x2000, cpu.SP);
191 |
192 | void UpdateMemory(ushort addr, byte data)
193 | {
194 | program[addr] = data;
195 | }
196 | }
197 |
198 | [Fact]
199 | private void PopProgramCounterForRETCC_GivenCarryFlagSet()
200 | {
201 | var fakeBus = A.Fake();
202 |
203 | var program = new Dictionary
204 | {
205 | // Program Code
206 | { 0x0080, 0x37 }, // SCF
207 | { 0x0081, 0xD8 }, // RET C
208 | { 0x0082, 0x00 },
209 | { 0x0083, 0x00 },
210 | { 0x0084, 0x00 },
211 |
212 | { 0x0190, 0x00 }, // <- Continue from here
213 | { 0x0191, 0x00 },
214 | { 0x0192, 0x00 },
215 |
216 | { 0x1FFB, 0x00 },
217 | { 0x1FFC, 0x00 },
218 | { 0x1FFD, 0x00 },
219 | { 0x1FFE, 0x90 },// <- SP
220 | { 0x1FFF, 0x01 },
221 | { 0x2000, 0x00 },
222 | };
223 |
224 | A.CallTo(() => fakeBus.Read(A._, A._))
225 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
226 | A.CallTo(() => fakeBus.Write(A._, A._))
227 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
228 |
229 | var cpu = new Z80() { A = 0x00, PC = 0x0080, SP = 0x1FFE };
230 | cpu.ConnectToBus(fakeBus);
231 |
232 | cpu.Step();
233 | cpu.Step();
234 |
235 | Assert.Equal(0x0190, cpu.PC);
236 | Assert.Equal(0x2000, cpu.SP);
237 |
238 | void UpdateMemory(ushort addr, byte data)
239 | {
240 | program[addr] = data;
241 | }
242 | }
243 |
244 | [Fact]
245 | private void DoNothingForRETCC_GivenCarryFlagNotSet()
246 | {
247 | var fakeBus = A.Fake();
248 |
249 | var program = new Dictionary
250 | {
251 | // Program Code
252 | { 0x0080, 0xD8 }, // RET C
253 | { 0x0081, 0x00 },
254 | { 0x0082, 0x00 },
255 | { 0x0083, 0x00 },
256 | { 0x0084, 0x00 },
257 |
258 | { 0x0190, 0x00 }, // <- Continue from here
259 | { 0x0191, 0x00 },
260 | { 0x0192, 0x00 },
261 |
262 | { 0x1FFB, 0x00 },
263 | { 0x1FFC, 0x00 },
264 | { 0x1FFD, 0x00 },
265 | { 0x1FFE, 0x90 },// <- SP
266 | { 0x1FFF, 0x01 },
267 | { 0x2000, 0x00 },
268 | };
269 |
270 | A.CallTo(() => fakeBus.Read(A._, A._))
271 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
272 | A.CallTo(() => fakeBus.Write(A._, A._))
273 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
274 |
275 | var cpu = new Z80() { A = 0x00, PC = 0x0080, SP = 0x1FFE };
276 | cpu.ConnectToBus(fakeBus);
277 |
278 | cpu.Step();
279 |
280 | Assert.Equal(0x0081, cpu.PC);
281 | Assert.Equal(0x1FFE, cpu.SP);
282 |
283 | void UpdateMemory(ushort addr, byte data)
284 | {
285 | program[addr] = data;
286 | }
287 | }
288 |
289 | [Fact]
290 | private void PushAndSetProgramCounterForRST()
291 | {
292 | var fakeBus = A.Fake();
293 |
294 | var program = new Dictionary
295 | {
296 | // Program Code
297 | { 0x0080, 0xFF }, // RST &38
298 | { 0x0081, 0x00 },
299 | { 0x0082, 0x00 },
300 | { 0x0083, 0x00 },
301 | { 0x0084, 0x00 },
302 |
303 | { 0x0190, 0x00 },
304 | { 0x0191, 0x00 },
305 | { 0x0192, 0x00 },
306 |
307 | { 0x1FFB, 0x00 },
308 | { 0x1FFC, 0x00 },
309 | { 0x1FFD, 0x00 },
310 | { 0x1FFE, 0x00 },
311 | { 0x1FFF, 0x00 },
312 | { 0x2000, 0x00 }, // <- SP
313 | };
314 |
315 | A.CallTo(() => fakeBus.Read(A._, A._))
316 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
317 | A.CallTo(() => fakeBus.Write(A._, A._))
318 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
319 |
320 | var cpu = new Z80() { A = 0x00, PC = 0x0080, SP = 0x2000 };
321 | cpu.ConnectToBus(fakeBus);
322 |
323 | cpu.Step();
324 |
325 | Assert.Equal(0x0038, cpu.PC);
326 | Assert.Equal(0x1FFE, cpu.SP);
327 | Assert.Equal(0x00, program[0x1FFF]);
328 | Assert.Equal(0x81, program[0x1FFE]);
329 |
330 | void UpdateMemory(ushort addr, byte data)
331 | {
332 | program[addr] = data;
333 | }
334 | }
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/Classes/BasicBus.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Essenbee.Z80.Tests.Classes
4 | {
5 | public class BasicBus : IBus
6 | {
7 | public bool Interrupt { get; set; }
8 | public bool NonMaskableInterrupt { get; set; }
9 | public IList Data { get; set; } = new List();
10 |
11 | private byte[] _memory;
12 | public BasicBus(int RAMSize)
13 | {
14 | _memory = new byte[RAMSize * 1024];
15 | }
16 |
17 | public BasicBus(byte[] ram)
18 | {
19 | _memory = ram;
20 | }
21 |
22 | public IReadOnlyCollection RAM
23 | {
24 | get => _memory;
25 | }
26 |
27 | public byte Read(ushort addr, bool ro = false)
28 | {
29 | return _memory[addr];
30 | }
31 |
32 | public byte ReadPeripheral(ushort port)
33 | {
34 | // Testing code only
35 | var r = (byte)(port >> 8);
36 | return r;
37 | }
38 |
39 | public void Write(ushort addr, byte data)
40 | {
41 | _memory[addr] = data;
42 | }
43 |
44 | public void WritePeripheral(ushort port, byte data)
45 | {
46 | // Testing code only
47 | _memory[port] = data;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/Classes/HexFileReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 |
5 | namespace Essenbee.Z80.Tests.Classes
6 | {
7 | public static class HexFileReader
8 | {
9 | public static byte[] Read(string filePath)
10 | {
11 | var RAM = new byte[48 * 1024];
12 | var lines = File.ReadAllLines(filePath);
13 |
14 | foreach (var line in lines)
15 | {
16 | if (line.Equals(":00000001FF", StringComparison.InvariantCultureIgnoreCase))
17 | {
18 | break;
19 | }
20 |
21 | var dataLength = Convert.ToInt32(line[1..3], 16);
22 | var startAddr = (ushort)Convert.ToInt32(line[3..7], 16);
23 | var recType = line[7..9];
24 |
25 | if (recType == "00")
26 | {
27 | // Data record
28 | var dataEnd = (2 * dataLength) + 9;
29 | var data = line[9..dataEnd];
30 | var dataBytes = new List();
31 |
32 | for (int i = 0; i < dataLength * 2; i++)
33 | {
34 | if (i == 0 || i % 2 == 0)
35 | {
36 | dataBytes.Add((byte)Convert.ToInt32(data.Substring(i, 2), 16));
37 | }
38 | }
39 |
40 | foreach (var datum in dataBytes)
41 | {
42 | RAM[startAddr++] = datum;
43 | }
44 | }
45 |
46 | if (recType == "02")
47 | {
48 | // ToDo: Extended segment address record
49 | }
50 |
51 | if (recType == "04")
52 | {
53 | // ToDo: Extended linear address record
54 | }
55 |
56 | if (recType == "05")
57 | {
58 | // ToDo: Start linear address record
59 | }
60 | }
61 |
62 | return RAM;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/Classes/Results.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Essenbee.Z80.Tests.Classes
4 | {
5 | public class Results
6 | {
7 | public List Passing { get; } = new List();
8 | public Dictionary> Failing { get; } = new Dictionary>();
9 | public List NotImplemented { get; } = new List();
10 |
11 | public Results(List passing, Dictionary> failing, List missing)
12 | {
13 | Passing = passing;
14 | Failing = failing;
15 | NotImplemented = missing;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/DisassemblerShould.cs:
--------------------------------------------------------------------------------
1 | using Essenbee.Z80.Tests.Classes;
2 | using FakeItEasy;
3 | using System.Collections.Generic;
4 | using Xunit;
5 |
6 | namespace Essenbee.Z80.Tests
7 | {
8 | public class DisassemblerShould
9 | {
10 | [Fact]
11 | private void DisassembleArithmetic1HexFileCorrectly()
12 | {
13 | var fakeBus = A.Fake();
14 |
15 | var expectedDisassembly = new Dictionary
16 | { { 0x0080, "LD A,&05" },
17 | { 0x0082, "LD B,&0A" },
18 | { 0x0084, "ADD A,B" },
19 | { 0x0085, "ADD A,A" },
20 | { 0x0086, "LD C,&0F" },
21 | { 0x0088, "SUB A,C" },
22 | { 0x0089, "LD H,&08" },
23 | { 0x008B, "LD L,&FF" },
24 | { 0x008D, "LD (HL),A" },
25 | { 0x008E, "NOP" },
26 | };
27 |
28 | var ram = HexFileReader.Read("../../../HexFiles/Arithmetic1.hex");
29 |
30 | A.CallTo(() => fakeBus.Read(A._, A._))
31 | .ReturnsLazily((ushort addr, bool ro) => ram[addr]);
32 |
33 | var cpu = new Z80() { A = 0x00, PC = 0x0080 };
34 | cpu.ConnectToBus(fakeBus);
35 | var disassembledCode = cpu.Disassemble(0x0080, 0x008E);
36 |
37 | Assert.Equal(expectedDisassembly, disassembledCode);
38 | }
39 |
40 | [Fact]
41 | private void DisassembleMultiplicationHexFileCorrectly()
42 | {
43 | var fakeBus = A.Fake();
44 | var ram = HexFileReader.Read("../../../HexFiles/Multiplication.hex");
45 |
46 | var expectedDisassembly = new Dictionary
47 | { { 0x8000, "LD BC,&0015" },
48 | { 0x8003, "LD B,&08" },
49 | { 0x8005, "LD DE,&002A" },
50 | { 0x8008, "LD D,&00" },
51 | { 0x800A, "LD HL,&0000" },
52 | { 0x800D, "SRL C" },
53 | { 0x800F, "JR NC,$+3" },
54 | { 0x8011, "ADD HL,DE" },
55 | { 0x8012, "SLA E" },
56 | { 0x8014, "RL D" },
57 | { 0x8016, "DEC B" },
58 | { 0x8017, "JP NZ,&800D" },
59 | };
60 |
61 | A.CallTo(() => fakeBus.Read(A._, A._))
62 | .ReturnsLazily((ushort addr, bool ro) => ram[addr]);
63 |
64 |
65 | var cpu = new Z80() { A = 0x00, PC = 0x8000 };
66 | cpu.ConnectToBus(fakeBus);
67 | var disassembledCode = cpu.Disassemble(0x8000, 0x8017);
68 |
69 | Assert.Equal(expectedDisassembly, disassembledCode);
70 | }
71 |
72 | [Fact]
73 | private void DisassembleDDCBandFDCBOpcodesCorrectly()
74 | {
75 | var fakeBus = A.Fake();
76 | var ram = HexFileReader.Read("../../../HexFiles/TestDDCBandFDCB.hex");
77 |
78 | var expectedDisassembly = new Dictionary
79 | { { 0x8000, "RL (IX+2)" },
80 | { 0x8004, "RL (IY-3)" },
81 | };
82 |
83 | A.CallTo(() => fakeBus.Read(A._, A._))
84 | .ReturnsLazily((ushort addr, bool ro) => ram[addr]);
85 |
86 |
87 | var cpu = new Z80() { A = 0x00, PC = 0x8000 };
88 | cpu.ConnectToBus(fakeBus);
89 | var disassembledCode = cpu.Disassemble(0x8000, 0x8007);
90 |
91 | Assert.Equal(expectedDisassembly, disassembledCode);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/Essenbee.Z80.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 | enable
6 | false
7 |
8 |
9 |
10 |
11 |
12 | all
13 | runtime; build; native; contentfiles; analyzers; buildtransitive
14 |
15 |
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers; buildtransitive
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/ExchangeShould.cs:
--------------------------------------------------------------------------------
1 | using FakeItEasy;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 | using Xunit;
6 | using static Essenbee.Z80.Z80;
7 |
8 | namespace Essenbee.Z80.Tests
9 | {
10 | public class ExchangeShould
11 | {
12 | private static void FlagsUnchanged(Z80 cpu)
13 | {
14 | Assert.False((cpu.F & Z80.Flags.C) == Z80.Flags.C);
15 | Assert.False((cpu.F & Z80.Flags.N) == Z80.Flags.N);
16 | Assert.False((cpu.F & Z80.Flags.P) == Z80.Flags.P);
17 | Assert.False((cpu.F & Z80.Flags.X) == Z80.Flags.X);
18 | Assert.False((cpu.F & Z80.Flags.H) == Z80.Flags.H);
19 | Assert.False((cpu.F & Z80.Flags.U) == Z80.Flags.U);
20 | Assert.False((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
21 | Assert.False((cpu.F & Z80.Flags.S) == Z80.Flags.S);
22 | }
23 |
24 | [Fact]
25 | private void SwapDEandHL()
26 | {
27 | var fakeBus = A.Fake();
28 |
29 | var program = new Dictionary
30 | {
31 | // Program Code
32 | { 0x0080, 0xEB }, // EX DE,HL
33 | { 0x0081, 0x00 },
34 | { 0x0082, 0x00 },
35 | { 0x0083, 0x00 },
36 | { 0x0084, 0x00 },
37 | };
38 |
39 | A.CallTo(() => fakeBus.Read(A._, A._))
40 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
41 |
42 | var cpu = new Z80() { D = 0x11, E = 0x22, H = 0x33, L = 0x44, PC = 0x0080 };
43 | cpu.ConnectToBus(fakeBus);
44 |
45 | cpu.Step();
46 |
47 | Assert.Equal(0x3344, cpu.DE);
48 | Assert.Equal(0x1122, cpu.HL);
49 | FlagsUnchanged(cpu);
50 | }
51 |
52 | [Fact]
53 | private void SwapAFandAFPrime()
54 | {
55 | var fakeBus = A.Fake();
56 |
57 | var program = new Dictionary
58 | {
59 | // Program Code
60 | { 0x0080, 0x08 }, // EX AF,AF'
61 | { 0x0081, 0x00 },
62 | { 0x0082, 0x00 },
63 | { 0x0083, 0x00 },
64 | { 0x0084, 0x00 },
65 | };
66 |
67 | A.CallTo(() => fakeBus.Read(A._, A._))
68 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
69 |
70 | var cpu = new Z80() { A = 0x11, F = (Flags)0x22, PC = 0x0080 };
71 | cpu.ConnectToBus(fakeBus);
72 |
73 | cpu.Step();
74 |
75 | Assert.Equal(0x0000, cpu.AF);
76 | Assert.Equal(0x1122, cpu.AF1);
77 | FlagsUnchanged(cpu);
78 | }
79 |
80 | [Fact]
81 | private void SwapRegistersWithEXX()
82 | {
83 | var fakeBus = A.Fake();
84 |
85 | var program = new Dictionary
86 | {
87 | // Program Code
88 | { 0x0080, 0xD9 }, // EXX
89 | { 0x0081, 0x00 },
90 | { 0x0082, 0x00 },
91 | { 0x0083, 0x00 },
92 | { 0x0084, 0x00 },
93 | };
94 |
95 | A.CallTo(() => fakeBus.Read(A._, A._))
96 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
97 |
98 | var cpu = new Z80() { B = 0x11, C = 0x22, D = 0x12, E = 0x23, H = 0x14, L = 0x24, PC = 0x0080 };
99 | cpu.ConnectToBus(fakeBus);
100 |
101 | cpu.Step();
102 |
103 | Assert.Equal(0x0000, cpu.BC);
104 | Assert.Equal(0x1122, cpu.BC1);
105 | Assert.Equal(0x0000, cpu.DE);
106 | Assert.Equal(0x1223, cpu.DE1);
107 | Assert.Equal(0x0000, cpu.HL);
108 | Assert.Equal(0x1424, cpu.HL1);
109 |
110 | FlagsUnchanged(cpu);
111 | }
112 |
113 | [Fact]
114 | private void SwapLocationPointedToBySPwithHLforEXSPHL()
115 | {
116 | var fakeBus = A.Fake();
117 |
118 | var program = new Dictionary
119 | {
120 | // Program Code
121 | { 0x0080, 0xE3 }, // EX (SP),HL
122 | { 0x0081, 0x00 },
123 | { 0x0082, 0x00 },
124 | { 0x0083, 0x00 },
125 | { 0x0084, 0x00 },
126 |
127 | // Data
128 | { 0x8855, 0x00 },
129 | { 0x8856, 0x11 },
130 | { 0x8857, 0x22 },
131 | { 0x8858, 0x00 },
132 | };
133 |
134 | A.CallTo(() => fakeBus.Read(A._, A._))
135 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
136 | A.CallTo(() => fakeBus.Write(A._, A._))
137 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
138 |
139 | var cpu = new Z80() { H = 0x70, L = 0x12, SP = 0x8856, PC = 0x0080 };
140 | cpu.ConnectToBus(fakeBus);
141 |
142 | cpu.Step();
143 |
144 | Assert.Equal(0x2211, cpu.HL);
145 | Assert.Equal(0x12, program[0x8856]);
146 | Assert.Equal(0x70, program[0x8857]);
147 | Assert.Equal(0x8856, cpu.SP);
148 |
149 | FlagsUnchanged(cpu);
150 |
151 | void UpdateMemory(ushort addr, byte data)
152 | {
153 | program[addr] = data;
154 | }
155 | }
156 |
157 | [Fact]
158 | private void SwapLocationPointedToBySPwithIXforEXSPIX()
159 | {
160 | var fakeBus = A.Fake();
161 |
162 | var program = new Dictionary
163 | {
164 | // Program Code
165 | { 0x0080, 0xDD }, // EX (SP),IX
166 | { 0x0081, 0xE3 },
167 | { 0x0082, 0x00 },
168 | { 0x0083, 0x00 },
169 | { 0x0084, 0x00 },
170 |
171 | // Data
172 | { 0x8855, 0x00 },
173 | { 0x8856, 0x11 },
174 | { 0x8857, 0x22 },
175 | { 0x8858, 0x00 },
176 | };
177 |
178 | A.CallTo(() => fakeBus.Read(A._, A._))
179 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
180 | A.CallTo(() => fakeBus.Write(A._, A._))
181 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
182 |
183 | var cpu = new Z80() { IX = 0x7012, SP = 0x8856, PC = 0x0080 };
184 | cpu.ConnectToBus(fakeBus);
185 |
186 | cpu.Step();
187 |
188 | Assert.Equal(0x2211, cpu.IX);
189 | Assert.Equal(0x12, program[0x8856]);
190 | Assert.Equal(0x70, program[0x8857]);
191 | Assert.Equal(0x8856, cpu.SP);
192 |
193 | FlagsUnchanged(cpu);
194 |
195 | void UpdateMemory(ushort addr, byte data)
196 | {
197 | program[addr] = data;
198 | }
199 | }
200 |
201 | [Fact]
202 | private void SwapLocationPointedToBySPwithIYforEXSPIY()
203 | {
204 | var fakeBus = A.Fake();
205 |
206 | var program = new Dictionary
207 | {
208 | // Program Code
209 | { 0x0080, 0xFD }, // EX (SP),IY
210 | { 0x0081, 0xE3 },
211 | { 0x0082, 0x00 },
212 | { 0x0083, 0x00 },
213 | { 0x0084, 0x00 },
214 |
215 | // Data
216 | { 0x8855, 0x00 },
217 | { 0x8856, 0x11 },
218 | { 0x8857, 0x22 },
219 | { 0x8858, 0x00 },
220 | };
221 |
222 | A.CallTo(() => fakeBus.Read(A._, A._))
223 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
224 | A.CallTo(() => fakeBus.Write(A._, A._))
225 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
226 |
227 | var cpu = new Z80() { IY = 0x7012, SP = 0x8856, PC = 0x0080 };
228 | cpu.ConnectToBus(fakeBus);
229 |
230 | cpu.Step();
231 |
232 | Assert.Equal(0x2211, cpu.IY);
233 | Assert.Equal(0x12, program[0x8856]);
234 | Assert.Equal(0x70, program[0x8857]);
235 | Assert.Equal(0x8856, cpu.SP);
236 |
237 | FlagsUnchanged(cpu);
238 |
239 | void UpdateMemory(ushort addr, byte data)
240 | {
241 | program[addr] = data;
242 | }
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/FlagsShould.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Xunit;
3 |
4 | namespace Essenbee.Z80.Tests
5 | {
6 | public class FlagsShould
7 | {
8 | [Flags]
9 | private enum Flags
10 | {
11 | C = 1 << 0,
12 | N = 1 << 1,
13 | P = 1 << 2,
14 | X = 1 << 3,
15 | H = 1 << 4,
16 | U = 1 << 5,
17 | Z = 1 << 6,
18 | S = 1 << 7,
19 | };
20 |
21 | private Flags _flagRegister;
22 |
23 | [Fact]
24 | public void InitiallyHaveAllFlagsNotSet()
25 | {
26 | _flagRegister = 0x00;
27 | Assert.False((_flagRegister & Flags.C) == Flags.C);
28 | Assert.False((_flagRegister & Flags.N) == Flags.N);
29 | Assert.False((_flagRegister & Flags.P) == Flags.P);
30 | Assert.False((_flagRegister & Flags.X) == Flags.X);
31 | Assert.False((_flagRegister & Flags.H) == Flags.H);
32 | Assert.False((_flagRegister & Flags.U) == Flags.U);
33 | Assert.False((_flagRegister & Flags.Z) == Flags.Z);
34 | Assert.False((_flagRegister & Flags.S) == Flags.S);
35 | }
36 |
37 | [Fact]
38 | public void SetCarryFlagOnly()
39 | {
40 | _flagRegister = 0x00;
41 |
42 | SetFlag(Flags.C, true);
43 |
44 | Assert.True((_flagRegister & Flags.C) == Flags.C);
45 | Assert.False((_flagRegister & Flags.N) == Flags.N);
46 | Assert.False((_flagRegister & Flags.P) == Flags.P);
47 | Assert.False((_flagRegister & Flags.X) == Flags.X);
48 | Assert.False((_flagRegister & Flags.H) == Flags.H);
49 | Assert.False((_flagRegister & Flags.U) == Flags.U);
50 | Assert.False((_flagRegister & Flags.Z) == Flags.Z);
51 | Assert.False((_flagRegister & Flags.S) == Flags.S);
52 | }
53 |
54 | [Fact]
55 | public void SetCarryAndSignFlagOnly()
56 | {
57 | _flagRegister = 0x00;
58 |
59 | SetFlag(Flags.C, true);
60 | SetFlag(Flags.S, true);
61 |
62 | Assert.True((_flagRegister & Flags.C) == Flags.C);
63 | Assert.False((_flagRegister & Flags.N) == Flags.N);
64 | Assert.False((_flagRegister & Flags.P) == Flags.P);
65 | Assert.False((_flagRegister & Flags.X) == Flags.X);
66 | Assert.False((_flagRegister & Flags.H) == Flags.H);
67 | Assert.False((_flagRegister & Flags.U) == Flags.U);
68 | Assert.False((_flagRegister & Flags.Z) == Flags.Z);
69 | Assert.True((_flagRegister & Flags.S) == Flags.S);
70 | }
71 |
72 | [Fact]
73 | public void UnSetZeroFlagOnly()
74 | {
75 | _flagRegister = Flags.C | Flags.N | Flags.P | Flags.H | Flags.Z | Flags.S;
76 |
77 | SetFlag(Flags.Z, false);
78 |
79 | Assert.True((_flagRegister & Flags.C) == Flags.C);
80 | Assert.True((_flagRegister & Flags.N) == Flags.N);
81 | Assert.True((_flagRegister & Flags.P) == Flags.P);
82 | Assert.False((_flagRegister & Flags.X) == Flags.X);
83 | Assert.True((_flagRegister & Flags.H) == Flags.H);
84 | Assert.False((_flagRegister & Flags.U) == Flags.U);
85 | Assert.False((_flagRegister & Flags.Z) == Flags.Z);
86 | Assert.True((_flagRegister & Flags.S) == Flags.S);
87 | }
88 |
89 | private void SetFlag(Flags flag, bool value)
90 | {
91 | if (value)
92 | {
93 | _flagRegister |= flag;
94 | }
95 | else
96 | {
97 | _flagRegister &= ~flag;
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/GeneralControlGroupShould.cs:
--------------------------------------------------------------------------------
1 | using FakeItEasy;
2 | using System.Collections.Generic;
3 | using Xunit;
4 | using static Essenbee.Z80.Z80;
5 |
6 | namespace Essenbee.Z80.Tests
7 | {
8 | public class GeneralControlGroupShould
9 | {
10 | [Fact]
11 | private void ProduceOnesComplementOfAccumulatorForCPL()
12 | {
13 | var fakeBus = A.Fake();
14 |
15 | var program = new Dictionary
16 | {
17 | // Program Code
18 | { 0x0080, 0x2F }, // CPL
19 | { 0x0081, 0x00 },
20 | { 0x0082, 0x00 },
21 | { 0x0083, 0x00 },
22 | { 0x0084, 0x00 },
23 | };
24 |
25 | A.CallTo(() => fakeBus.Read(A._, A._))
26 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
27 |
28 | var cpu = new Z80() { A = 0b01010101, PC = 0x0080 };
29 | cpu.ConnectToBus(fakeBus);
30 |
31 | cpu.Step();
32 |
33 | Assert.Equal(0b10101010, cpu.A);
34 | Assert.True((cpu.F & Z80.Flags.N) == Z80.Flags.N); // Set
35 | Assert.False((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
36 | Assert.False((cpu.F & Z80.Flags.S) == Z80.Flags.S);
37 | Assert.True((cpu.F & Z80.Flags.H) == Z80.Flags.H); // Set
38 | Assert.False((cpu.F & Z80.Flags.P) == Z80.Flags.P);
39 | Assert.False((cpu.F & Z80.Flags.C) == Z80.Flags.C);
40 | Assert.True((cpu.F & Z80.Flags.U) == Z80.Flags.U);
41 | Assert.True((cpu.F & Z80.Flags.X) == Z80.Flags.X);
42 | }
43 |
44 | [Fact]
45 | private void ProduceTwosComplementOfAccumulatorForNEG()
46 | {
47 | var fakeBus = A.Fake();
48 |
49 | var program = new Dictionary
50 | {
51 | // Program Code
52 | { 0x0080, 0xED }, // NEG
53 | { 0x0081, 0x44 },
54 | { 0x0082, 0x00 },
55 | { 0x0083, 0x00 },
56 | { 0x0084, 0x00 },
57 | };
58 |
59 | A.CallTo(() => fakeBus.Read(A._, A._))
60 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
61 |
62 | var cpu = new Z80() { A = 0b10011000, PC = 0x0080 };
63 | cpu.ConnectToBus(fakeBus);
64 |
65 | cpu.Step();
66 |
67 | Assert.Equal(0b01101000, cpu.A);
68 | Assert.True((cpu.F & Z80.Flags.N) == Z80.Flags.N); // Set
69 | Assert.False((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
70 | Assert.False((cpu.F & Z80.Flags.S) == Z80.Flags.S);
71 | Assert.True((cpu.F & Z80.Flags.H) == Z80.Flags.H);
72 | Assert.False((cpu.F & Z80.Flags.P) == Z80.Flags.P);
73 | Assert.True((cpu.F & Z80.Flags.C) == Z80.Flags.C);
74 | Assert.True((cpu.F & Z80.Flags.U) == Z80.Flags.U);
75 | Assert.True((cpu.F & Z80.Flags.X) == Z80.Flags.X);
76 | }
77 |
78 | [Fact]
79 | private void ComplementCarryFlag()
80 | {
81 | var fakeBus = A.Fake();
82 |
83 | var program = new Dictionary
84 | {
85 | // Program Code
86 | { 0x0080, 0x3F }, // CCF
87 | { 0x0081, 0x00 },
88 | { 0x0082, 0x00 },
89 | { 0x0083, 0x00 },
90 | { 0x0084, 0x00 },
91 | };
92 |
93 | A.CallTo(() => fakeBus.Read(A._, A._))
94 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
95 |
96 | var cpu = new Z80() { A = 0x00, F = (Flags)0b00000001, PC = 0x0080 };
97 | cpu.ConnectToBus(fakeBus);
98 |
99 | cpu.Step();
100 |
101 | Assert.False((cpu.F & Z80.Flags.N) == Z80.Flags.N);
102 | Assert.False((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
103 | Assert.False((cpu.F & Z80.Flags.S) == Z80.Flags.S);
104 | Assert.True((cpu.F & Z80.Flags.H) == Z80.Flags.H);
105 | Assert.False((cpu.F & Z80.Flags.P) == Z80.Flags.P);
106 | Assert.False((cpu.F & Z80.Flags.C) == Z80.Flags.C);
107 | }
108 |
109 | [Fact]
110 | private void SetCarryFlag()
111 | {
112 | var fakeBus = A.Fake();
113 |
114 | var program = new Dictionary
115 | {
116 | // Program Code
117 | { 0x0080, 0x37 }, // SCF
118 | { 0x0081, 0x00 },
119 | { 0x0082, 0x00 },
120 | { 0x0083, 0x00 },
121 | { 0x0084, 0x00 },
122 | };
123 |
124 | A.CallTo(() => fakeBus.Read(A._, A._))
125 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
126 |
127 | var cpu = new Z80() { A = 0x00, F = (Flags)0b00000000, PC = 0x0080 };
128 | cpu.ConnectToBus(fakeBus);
129 |
130 | cpu.Step();
131 |
132 | Assert.False((cpu.F & Z80.Flags.N) == Z80.Flags.N);
133 | Assert.False((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
134 | Assert.False((cpu.F & Z80.Flags.S) == Z80.Flags.S);
135 | Assert.False((cpu.F & Z80.Flags.H) == Z80.Flags.H);
136 | Assert.False((cpu.F & Z80.Flags.P) == Z80.Flags.P);
137 | Assert.True((cpu.F & Z80.Flags.C) == Z80.Flags.C);
138 | }
139 |
140 | [Fact]
141 | private void SetCarryFlagUndocumentedFlags1()
142 | {
143 | var fakeBus = A.Fake();
144 |
145 | var program = new Dictionary
146 | {
147 | // Program Code
148 | { 0x0080, 0x37 }, // SCF
149 | { 0x0081, 0x00 },
150 | { 0x0082, 0x00 },
151 | { 0x0083, 0x00 },
152 | { 0x0084, 0x00 },
153 | };
154 |
155 | A.CallTo(() => fakeBus.Read(A._, A._))
156 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
157 |
158 | var cpu = new Z80() { A = 0x00, F = (Flags)0b1111_1111, PC = 0x0080 };
159 | cpu.ConnectToBus(fakeBus);
160 |
161 | cpu.Step();
162 |
163 | Assert.True(cpu.A == 0x00);
164 | Assert.True((cpu.F & Z80.Flags.S) == Z80.Flags.S);
165 | Assert.True((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
166 | Assert.True((cpu.F & Z80.Flags.U) == Z80.Flags.U);
167 | Assert.False((cpu.F & Z80.Flags.H) == Z80.Flags.H);
168 | Assert.True((cpu.F & Z80.Flags.X) == Z80.Flags.X);
169 | Assert.True((cpu.F & Z80.Flags.P) == Z80.Flags.P);
170 | Assert.False((cpu.F & Z80.Flags.N) == Z80.Flags.N);
171 | Assert.True((cpu.F & Z80.Flags.C) == Z80.Flags.C);
172 | }
173 |
174 | [Fact]
175 | private void SetCarryFlagUndocumentedFlags2()
176 | {
177 | var fakeBus = A.Fake();
178 |
179 | var program = new Dictionary
180 | {
181 | // Program Code
182 | { 0x0080, 0x37 }, // SCF
183 | { 0x0081, 0x00 },
184 | { 0x0082, 0x00 },
185 | { 0x0083, 0x00 },
186 | { 0x0084, 0x00 },
187 | };
188 |
189 | A.CallTo(() => fakeBus.Read(A._, A._))
190 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
191 |
192 | var cpu = new Z80() { A = 0xFF, F = (Flags)0b0000_0000, PC = 0x0080 };
193 | cpu.ConnectToBus(fakeBus);
194 |
195 | cpu.Step();
196 |
197 | Assert.True(cpu.A == 0xFF);
198 | Assert.False((cpu.F & Z80.Flags.S) == Z80.Flags.S);
199 | Assert.False((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
200 | Assert.True((cpu.F & Z80.Flags.U) == Z80.Flags.U);
201 | Assert.False((cpu.F & Z80.Flags.H) == Z80.Flags.H);
202 | Assert.True((cpu.F & Z80.Flags.X) == Z80.Flags.X);
203 | Assert.False((cpu.F & Z80.Flags.P) == Z80.Flags.P);
204 | Assert.False((cpu.F & Z80.Flags.N) == Z80.Flags.N);
205 | Assert.True((cpu.F & Z80.Flags.C) == Z80.Flags.C);
206 | }
207 |
208 | [Fact]
209 | private void SetCarryFlagUndocumentedFlags3()
210 | {
211 | var fakeBus = A.Fake();
212 |
213 | var program = new Dictionary
214 | {
215 | // Program Code
216 | { 0x0080, 0x37 }, // SCF
217 | { 0x0081, 0x00 },
218 | { 0x0082, 0x00 },
219 | { 0x0083, 0x00 },
220 | { 0x0084, 0x00 },
221 | };
222 |
223 | A.CallTo(() => fakeBus.Read(A._, A._))
224 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
225 |
226 | var cpu = new Z80() { A = 0xFF, F = (Flags)0b1111_1111, PC = 0x0080 };
227 | cpu.ConnectToBus(fakeBus);
228 |
229 | cpu.Step();
230 |
231 | Assert.True(cpu.A == 0xFF);
232 | Assert.True((cpu.F & Z80.Flags.S) == Z80.Flags.S);
233 | Assert.True((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
234 | Assert.True((cpu.F & Z80.Flags.U) == Z80.Flags.U);
235 | Assert.False((cpu.F & Z80.Flags.H) == Z80.Flags.H);
236 | Assert.True((cpu.F & Z80.Flags.X) == Z80.Flags.X);
237 | Assert.True((cpu.F & Z80.Flags.P) == Z80.Flags.P);
238 | Assert.False((cpu.F & Z80.Flags.N) == Z80.Flags.N);
239 | Assert.True((cpu.F & Z80.Flags.C) == Z80.Flags.C); ;
240 | }
241 |
242 | [Fact]
243 | private void DisableInterruptByResettingIFF()
244 | {
245 | var fakeBus = A.Fake();
246 |
247 | var program = new Dictionary
248 | {
249 | // Program Code
250 | { 0x0080, 0xF3 }, // DI
251 | { 0x0081, 0x00 },
252 | { 0x0082, 0x00 },
253 | { 0x0083, 0x00 },
254 | { 0x0084, 0x00 },
255 | };
256 |
257 | A.CallTo(() => fakeBus.Read(A._, A._))
258 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
259 |
260 | var cpu = new Z80() { A = 0x00, PC = 0x0080 };
261 | cpu.ConnectToBus(fakeBus);
262 |
263 | cpu.Step();
264 |
265 | Assert.False(cpu.IFF1);
266 | Assert.False(cpu.IFF2);
267 | }
268 |
269 | [Fact]
270 | private void EnableInterruptBySettingIFF()
271 | {
272 | var fakeBus = A.Fake();
273 |
274 | var program = new Dictionary
275 | {
276 | // Program Code
277 | { 0x0080, 0xFB }, // EI
278 | { 0x0081, 0x00 },
279 | { 0x0082, 0x00 },
280 | { 0x0083, 0x00 },
281 | { 0x0084, 0x00 },
282 | };
283 |
284 | A.CallTo(() => fakeBus.Read(A._, A._))
285 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
286 |
287 | var cpu = new Z80() { A = 0x00, PC = 0x0080 };
288 | cpu.ConnectToBus(fakeBus);
289 |
290 | cpu.Step();
291 |
292 | Assert.True(cpu.IFF1);
293 | Assert.True(cpu.IFF2);
294 | }
295 |
296 | [Fact]
297 | private void SetInterruptMode0()
298 | {
299 | var fakeBus = A.Fake();
300 |
301 | var program = new Dictionary
302 | {
303 | // Program Code
304 | { 0x0080, 0xED }, // IM 0
305 | { 0x0081, 0x46 },
306 | { 0x0082, 0x00 },
307 | { 0x0083, 0x00 },
308 | { 0x0084, 0x00 },
309 | };
310 |
311 | A.CallTo(() => fakeBus.Read(A._, A._))
312 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
313 |
314 | var cpu = new Z80() { A = 0x00, PC = 0x0080 };
315 | cpu.ConnectToBus(fakeBus);
316 |
317 | cpu.Step();
318 |
319 | Assert.True(cpu.InterruptMode == InterruptMode.Mode0);
320 | }
321 |
322 | [Fact]
323 | private void SetInterruptMode1()
324 | {
325 | var fakeBus = A.Fake();
326 |
327 | var program = new Dictionary
328 | {
329 | // Program Code
330 | { 0x0080, 0xED }, // IM 1
331 | { 0x0081, 0x56 },
332 | { 0x0082, 0x00 },
333 | { 0x0083, 0x00 },
334 | { 0x0084, 0x00 },
335 | };
336 |
337 | A.CallTo(() => fakeBus.Read(A._, A._))
338 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
339 |
340 | var cpu = new Z80() { A = 0x00, PC = 0x0080 };
341 | cpu.ConnectToBus(fakeBus);
342 |
343 | cpu.Step();
344 |
345 | Assert.True(cpu.InterruptMode == InterruptMode.Mode1);
346 | }
347 |
348 | [Fact]
349 | private void SetInterruptMode2()
350 | {
351 | var fakeBus = A.Fake();
352 |
353 | var program = new Dictionary
354 | {
355 | // Program Code
356 | { 0x0080, 0xED }, // IM 2
357 | { 0x0081, 0x5E },
358 | { 0x0082, 0x00 },
359 | { 0x0083, 0x00 },
360 | { 0x0084, 0x00 },
361 | };
362 |
363 | A.CallTo(() => fakeBus.Read(A._, A._))
364 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
365 |
366 | var cpu = new Z80() { A = 0x00, PC = 0x0080 };
367 | cpu.ConnectToBus(fakeBus);
368 |
369 | cpu.Step();
370 |
371 | Assert.True(cpu.InterruptMode == InterruptMode.Mode2);
372 | }
373 | }
374 | }
375 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/HexFileReaderTests.cs:
--------------------------------------------------------------------------------
1 | using Essenbee.Z80.Tests.Classes;
2 | using FakeItEasy;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using Xunit;
7 |
8 | namespace Essenbee.Z80.Tests
9 | {
10 | public class HexFileReaderTests
11 | {
12 | [Fact]
13 | private void ReadSimpleHexFileWithOnlySingleDataRecord()
14 | {
15 | var fakeBus = A.Fake();
16 |
17 | // Routine #1 - 58 T-Cycles
18 | // .ORG 0080h
19 | //
20 | // LD A,05h
21 | // LD B,0Ah
22 | // ADD A, B
23 | // ADD A, A
24 | // LD C,0Fh
25 | // SUB C
26 | // LD H,08h
27 | // LD L,0FFh
28 | // LD(HL),A
29 | // NOP
30 |
31 | var ram = HexFileReader.Read("../../../HexFiles/Arithmetic1.hex");
32 |
33 | A.CallTo(() => fakeBus.Read(A._, A._))
34 | .ReturnsLazily((ushort addr, bool ro) => ram[addr]);
35 | A.CallTo(() => fakeBus.Write(A._, A._))
36 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
37 |
38 | var cpu = new Z80() { A = 0x00, B = 0x00, C = 0x00, H = 0x00, L = 0x00, PC = 0x0080 };
39 | cpu.ConnectToBus(fakeBus);
40 |
41 | for (int i = 0; i < 10; i++)
42 | {
43 | cpu.Step();
44 | }
45 |
46 | Assert.Equal(0x0F, ram[0x08FF]);
47 |
48 | void UpdateMemory(ushort addr, byte data)
49 | {
50 | ram[addr] = data;
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/HexFiles/Arithmetic1.hex:
--------------------------------------------------------------------------------
1 | :0F0080003E05060A80870E0F9126082EFF770097
2 | :00000001FF
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/HexFiles/ExampleMonitor.hex:
--------------------------------------------------------------------------------
1 | :1000000031F03EF3C38B0000000000000000000050
2 | :10002000C300210000000000C328200000000000E1
3 | :10003000C3302000000000002A403FE9F3DB01321A
4 | :10004000033FE60F32053F3A033FFE23CA22023A3E
5 | :10005000033FE680FE80CCFA02DB01FE22CC8A015F
6 | :10006000DB01FE21CC94013A1C3F321D3F3A053F93
7 | :10007000321C3FCDBF02CD6A033A033FFE27CCEFCF
8 | :10008000023A033FFE2BCC6D02ED4D3E00321C3F89
9 | :10009000321D3F3EC9321F3F3E82D3033E90D307FD
10 | :1000A0003EFFD305213C0022403FC37E01060F21C5
11 | :1000B000BC00110C3F7E12231310FAC901FF02FF8E
12 | :1000C00004FF08FF10FF20FF40FF80FF000000003A
13 | :10010000C0F9A4B0999282F880908883C6A1868EA7
14 | :1001100026013A063F6F7E320D3F3A073F6F7E322F
15 | :100120000F3F3A083F6F7E32113F3A093F6F7E32F0
16 | :10013000133F3A0A3F6F7E32153F3A0B3F6F7E32D4
17 | :10014000173F3A1C3F6F7E32193F3A1D3F6F7E3298
18 | :100150001B3FC9F3DB0132033FE60F32053FCD6998
19 | :1001600001CDBF02CD6A03ED4D0603210A3F110BFD
20 | :100170003F7E122B1B10FA3A053F32083FC9219DE2
21 | :1001800004CD5A04CDAD00C37902F3217D04CD5ACC
22 | :1001900004C30904F3218D04CD5A0421BD04CD5AB2
23 | :1001A000043E00320C3F320E3F32183F321A3F21DC
24 | :1001B000530122403FCD2D03CD1001DB01E6F0FEBF
25 | :1001C00010CACF01CDBF02FB000000F3C3B501F39D
26 | :1001D000CDBF022A013F22423F21CD04CD5A043E29
27 | :1001E00000320C3F320E3F32183F321A3FCD2D0302
28 | :1001F000CD1001DB01E6F0FE10CA0802CDBF02FB04
29 | :1002000000000000F3C3ED01F3CDBF022A013F223D
30 | :10021000443F213C0022403F2A423FED5B443FC324
31 | :10022000DD04F321AD04CD5A0421BD04CD5A043EB2
32 | :1002300000320C3F320E3F32183F321A3F21530139
33 | :1002400022403FCD2D03CD1001DB01E6F0FE10CAA8
34 | :100250005E02CDBF02FB00000000F3C34302F3CDFA
35 | :10026000BF02213C0022403FFB2A013FE9CDBF02F3
36 | :100270002A013F2322013FC9E7ED56F331F03E3E0C
37 | :100280000032013F3E2132023F2A013F7E32003FD1
38 | :10029000CDB3023A043FFE01CCCC02CD2D03CD10EC
39 | :1002A00001CD0F0331F03EFB00000000F3CD1F3FF6
40 | :1002B000C38902DB01E6F00F0F0F0F32043FC911B3
41 | :1002C0003500CD0F031B7AB3C2C202C93A1D3F17D6
42 | :1002D000171717211C3F8E321E3F2A013F3A1E3F3F
43 | :1002E000772A013F7E32003FCD8703CD6D02C92AB8
44 | :1002F000013F2B22013FCDBF02C90603210A3F1156
45 | :100300000B3F7E122B1B10FA3A053F32083FC9E51E
46 | :10031000C50E08210C3F7ED300237ED3022306FFA7
47 | :100320000010FD0D20F03E00D300C1E1C93A003FAE
48 | :10033000E60F32063F3A003FE6F01F1F1F1F32074D
49 | :100340003F3A013FE60F32083F3A013FE6F01F1FF8
50 | :100350001F1F32093F3A023FE60F320A3F3A023F7F
51 | :10036000E6F01F1F1F1F320B3FC93A093F1717172F
52 | :100370001721083F8E32013F3A0B3F1717171721FD
53 | :100380000A3F8E32023FC9CD2D03CD1001117000FE
54 | :10039000CD0F031B7AB3C29003C9F5D5C5E5213053
55 | :1003A0003F773E00D30500CDC9037E4F060879D3C1
56 | :1003B0000500CB09CDC90310F53EFFD30500CDC91B
57 | :1003C00003CDC903E1C1D1F1C91160001B7AB3C2E9
58 | :1003D000CC03C9E5D5C521303FDB0400E601FE01B1
59 | :1003E00028F7CDFF0336000608DB0400E601867718
60 | :1003F000CB0ECDC90310F221303F7EC1D1E1C9112E
61 | :1004000090001B7AB3C20204C93E0032313FCDD303
62 | :1004100003FE3A20F9CD430447CD430467CD43049E
63 | :100420006F3A313FFE01CA31043E0132313F2201B1
64 | :100430003FCD4304FE01CA8902CD4304772310F95E
65 | :1004400018CCC91600CD4D048787878757CDD303B5
66 | :10045000D630FE0A3802D607B2C9E5D5C5F5110C6B
67 | :100460003F06107E12231310FA118A00CD0F031BD2
68 | :100470007AB3C26C04CDAD00F1C1D1E1C901FF0274
69 | :10048000FF042108201023204740FF80FF01FF02C6
70 | :10049000FF040408631020201240FF80FF012402A3
71 | :1004A0006304FF0804100E200440FF80FF01FF02D8
72 | :1004B000FF04FF08481041204E40FF80FF012F023B
73 | :1004C00021040808FF1007201240FF80FF012102CD
74 | :1004D0000804FF0821102B200640FF80FFE5EBED0C
75 | :1004E00052EBE13E0032343F06103E0DCD9A033E02
76 | :1004F0000ACD9A037AFE00CAFD04C30A057BD61012
77 | :10050000DA0605C30A0543C30C0506103E3ACD9A28
78 | :100510000378CD60057CCD60057DCD60053E00CDC6
79 | :100520006005CD48057AB3C2F4043E3ACD9A03067D
80 | :10053000073E30CD9A0310F93E31CD9A033EFFCDF0
81 | :100540006005CDAD00C379027ECD6005231B10F898
82 | :100550003EFFCD60053E0DCD9A033E0ACD9A03C9FC
83 | :10056000C5D5E5F5E6F01F1F1F1F32333FCD7F05D0
84 | :10057000F1F5E60F32323FCD7F05F1E1D1C1C9215E
85 | :100580008C0506004FED4A7ECD9A03C930313233D7
86 | :0C05900034353637383941424344454683
87 | :00000001FF
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/HexFiles/HexFileFormat.txt:
--------------------------------------------------------------------------------
1 | Hex File Format Overview
2 | ========================
3 |
4 | :LLAAAATT[DD ...]CC
5 |
6 | : = start of line
7 | LL = number of DD data bytes in record
8 | AAAA = start address
9 | TT = record type:
10 | 00 = data
11 | 01 = end-of-file
12 | 02 = extended segment address record
13 | 04 = extended linear address record
14 | 05 = start linear address record
15 | DD = data byte (there will be LL of these in the record)
16 | CC = checksum
17 |
18 | :0F0080003E05060A80870E0F9126082EFF770097
19 | ||||||||||| CC->Checksum
20 | |||||||||DD->Data
21 | |||||||TT->Record Type
22 | |||AAAA->Address
23 | |LL->Record Length
24 | :->Colon
25 |
26 | End-ofFile Records
27 | ------------------
28 |
29 | These always look like this:
30 |
31 | :00000001FF
32 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/HexFiles/Multiplication.hex:
--------------------------------------------------------------------------------
1 | :108000000115000608112A001600210000CB3930A6
2 | :0A8010000119CB23CB1205C20D802D
3 | :00000001FF
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/HexFiles/Multiplication2.hex:
--------------------------------------------------------------------------------
1 | :108000000115000608112A001600210000CB3930A6
2 | :088010000119CB23CB1210F57E
3 | :00000001FF
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/HexFiles/TestDDCBandFDCB.hex:
--------------------------------------------------------------------------------
1 | :08800000DDCB0216FDCBFD16DD
2 | :00000001FF
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/InputOutputShould.cs:
--------------------------------------------------------------------------------
1 | using FakeItEasy;
2 | using System.Collections.Generic;
3 | using Xunit;
4 |
5 | namespace Essenbee.Z80.Tests
6 | {
7 | public class InputOutputShould
8 | {
9 | private static void FlagsUnchanged(Z80 cpu)
10 | {
11 | Assert.False((cpu.F & Z80.Flags.C) == Z80.Flags.C);
12 | Assert.False((cpu.F & Z80.Flags.N) == Z80.Flags.N);
13 | Assert.False((cpu.F & Z80.Flags.P) == Z80.Flags.P);
14 | Assert.False((cpu.F & Z80.Flags.X) == Z80.Flags.X);
15 | Assert.False((cpu.F & Z80.Flags.H) == Z80.Flags.H);
16 | Assert.False((cpu.F & Z80.Flags.U) == Z80.Flags.U);
17 | Assert.False((cpu.F & Z80.Flags.Z) == Z80.Flags.Z);
18 | Assert.False((cpu.F & Z80.Flags.S) == Z80.Flags.S);
19 | }
20 |
21 | [Fact]
22 | private void ReadBtyeFromPortForINA()
23 | {
24 | var fakeBus = A.Fake();
25 |
26 | var program = new Dictionary
27 | {
28 | // Program Code
29 | { 0x0080, 0xDB }, // IN A,(&01)
30 | { 0x0081, 0x01 },
31 | { 0x0082, 0x00 },
32 | { 0x0083, 0x00 },
33 | { 0x0084, 0x00 },
34 |
35 | // Data
36 | { 0x0000, 0x00 },
37 | { 0x0001, 0x7B },
38 | { 0x0002, 0x00 },
39 | { 0x0003, 0x00 },
40 | };
41 |
42 | A.CallTo(() => fakeBus.Read(A._, A._))
43 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
44 | A.CallTo(() => fakeBus.ReadPeripheral(A._))
45 | .ReturnsLazily((ushort addr) => program[addr]);
46 |
47 | var cpu = new Z80() { A = 0x00, PC = 0x0080 };
48 | cpu.ConnectToBus(fakeBus);
49 |
50 | cpu.Step();
51 |
52 | Assert.Equal(0x7B, cpu.A);
53 |
54 | FlagsUnchanged(cpu);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/IsSupportedShould.cs:
--------------------------------------------------------------------------------
1 | using FakeItEasy;
2 | using System.Collections.Generic;
3 | using Xunit;
4 |
5 | namespace Essenbee.Z80.Tests
6 | {
7 | public class IsSupportedShould
8 | {
9 | [Fact]
10 | private void ReturnTrueForNOP()
11 | {
12 | var cpu = new Z80();
13 |
14 | var isSupported = cpu.IsOpCodeSupported("00");
15 |
16 | Assert.True(isSupported);
17 | }
18 |
19 | [Fact]
20 | private void ReturnTrueForDD09()
21 | {
22 | var cpu = new Z80();
23 |
24 | var isSupported = cpu.IsOpCodeSupported("dd09");
25 |
26 | Assert.True(isSupported);
27 | }
28 |
29 | [Fact]
30 | private void ReturnTrueForDD70()
31 | {
32 | var cpu = new Z80();
33 |
34 | var isSupported = cpu.IsOpCodeSupported("DD70");
35 |
36 | Assert.True(isSupported);
37 | }
38 |
39 | [Fact]
40 | private void ReturnTrueForDDCB06()
41 | {
42 | var cpu = new Z80();
43 |
44 | // DDCB instructions are in the format DDCB{displacement}{opcode}
45 | var isSupported = cpu.IsOpCodeSupported("DDCB0206");
46 |
47 | Assert.True(isSupported);
48 | }
49 |
50 | [Fact]
51 | private void ReturnTrueForFDCBO206()
52 | {
53 | var cpu = new Z80();
54 |
55 | // FDCB instructions are in the format FDCB{displacement}{opcode}
56 | var isSupported = cpu.IsOpCodeSupported("FDCB0206");
57 |
58 | Assert.True(isSupported);
59 | }
60 |
61 | [Fact]
62 | private void ReturnTrueForFDCB06()
63 | {
64 | var cpu = new Z80();
65 |
66 | // FDCB instructions are in the format FDCB{opcode} in FUSE tester
67 | var isSupported = cpu.IsOpCodeSupported("fdcb06");
68 |
69 | Assert.True(isSupported);
70 | }
71 |
72 | [Fact]
73 | private void ReturnFalseForDD00()
74 | {
75 | var cpu = new Z80();
76 |
77 | var isSupported = cpu.IsOpCodeSupported("DD00");
78 |
79 | Assert.False(isSupported);
80 | }
81 |
82 | [Fact]
83 | private void ReturnFalseForED()
84 | {
85 | var cpu = new Z80();
86 |
87 | var isSupported = cpu.IsOpCodeSupported("ED");
88 |
89 | Assert.False(isSupported);
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Essenbee.Z80.Tests/Z80EmulatorShould.cs:
--------------------------------------------------------------------------------
1 | using Essenbee.Z80.Tests.Classes;
2 | using FakeItEasy;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using Xunit;
7 |
8 | // ===============================================================
9 | // Online Z80 Assembler: https://www.asm80.com/onepage/asmz80.html
10 | // ===============================================================
11 |
12 | namespace Essenbee.Z80.Tests
13 | {
14 | public class Z80EmulatorShould
15 | {
16 | [Fact]
17 | private void PassAllValidationTests()
18 | {
19 | var tester = new FuseTester();
20 | var results = tester.RunTests();
21 |
22 | Debug.WriteLine($"Passing tests = {results.Passing.Count}");
23 | Debug.WriteLine($"Failing tests = {results.Failing.Count}");
24 | Debug.WriteLine($"Opcodes not implemented = {results.NotImplemented.Count}");
25 |
26 | Assert.Empty(results.Failing);
27 | }
28 |
29 | [Fact]
30 | private void ExecuteArithmeticTestRoutine1Successfully()
31 | {
32 | var fakeBus = A.Fake();
33 |
34 | //` Arithmetic Test Routine #1 - 10 instructions
35 | //` Filename: Arithmetic1.hex
36 | //`
37 | //` 0080 .ORG 0080h
38 | //`
39 | //` 0080 3E 05 LD A,05h
40 | //` 0082 06 0A LD B,0Ah
41 | //` 0084 80 ADD A,B
42 | //` 0085 87 ADD A,A
43 | //` 0086 0E 0F LD C,0Fh
44 | //` 0088 91 SUB C
45 | //` 0089 26 08 LD H,08h
46 | //` 008B 2E FF LD L,0FFh
47 | //` 008D 77 LD (HL),A
48 | //` 008E 00 NOP
49 |
50 | var ram = HexFileReader.Read("../../../HexFiles/Arithmetic1.hex");
51 |
52 | A.CallTo(() => fakeBus.Read(A._, A._))
53 | .ReturnsLazily((ushort addr, bool ro) => ram[addr]);
54 | A.CallTo(() => fakeBus.Write(A._, A._))
55 | .Invokes((ushort addr, byte data) => UpdateMemory(addr, data));
56 |
57 | var cpu = new Z80() { A = 0x00, B = 0x00, C = 0x00, H = 0x00, L = 0x00, PC = 0x0080 };
58 | cpu.ConnectToBus(fakeBus);
59 |
60 | // Run 10 instructions
61 | for (int i = 0; i < 10; i++)
62 | {
63 | cpu.Step();
64 | Debug.WriteLine($"A = {cpu.A} B = {cpu.B} C = {cpu.C} H = {cpu.H} L = {cpu.L}");
65 | }
66 |
67 | Assert.Equal(0x0F, ram[0x08FF]);
68 |
69 | void UpdateMemory(ushort addr, byte data)
70 | {
71 | ram[addr] = data;
72 | }
73 | }
74 |
75 | [Fact]
76 | private void ExecuteEightBitMultiplicationRoutineSuccessfully()
77 | {
78 | var fakeBus = A.Fake();
79 |
80 | //` Arithmetic Test Routine #2
81 | //` Filename: Multiplication.hex
82 | //`
83 | //` 8000 .ORG 8000h
84 | //`
85 | //`8000 01 15 00 LD BC,21
86 | //`8003 06 08 LD B,8
87 | //`8005 11 2A 00 LD DE,42
88 | //`8008 16 00 LD D,0
89 | //`800A 21 00 00 LD HL,0
90 | //`800D CB 39 MULTI: SRL C; LSB in Carry Flag
91 | //`800F 30 01 JR NC, NOADD
92 | //`8011 19 ADD HL, DE
93 | //`8012 CB 23 NOADD: SLA E
94 | //`8014 CB 12 RL D
95 | //`8016 05 DEC B
96 | //`8017 C2 0D 80 JP NZ, MULTI
97 |
98 | var ram = HexFileReader.Read("../../../HexFiles/Multiplication.hex");
99 |
100 | A.CallTo(() => fakeBus.Read(A._, A._))
101 | .ReturnsLazily((ushort addr, bool ro) => ram[addr]);
102 |
103 | var cpu = new Z80() { A = 0x00, B = 0x00, C = 0x00, H = 0x00, L = 0x00, PC = 0x8000 };
104 | cpu.ConnectToBus(fakeBus);
105 |
106 | while (cpu.PC < 0x8020)
107 | {
108 | cpu.Step();
109 | }
110 |
111 | Assert.Equal(0x0372, cpu.HL);
112 | }
113 |
114 |
115 | [Fact]
116 | private void ExecuteEightBitMultiplication2RoutineSuccessfully()
117 | {
118 | var fakeBus = A.Fake();
119 |
120 | //` Arithmetic Test Routine #3
121 | //` Filename: Multiplication2.hex
122 | //`
123 | //` 8000 .ORG 8000h
124 | //`
125 | //`8000 01 15 00 LD BC,21
126 | //`8003 06 08 LD B,8
127 | //`8005 11 2A 00 LD DE,42
128 | //`8008 16 00 LD D,0
129 | //`800A 21 00 00 LD HL,0
130 | //`800D CB 39 MULTI: SRL C; LSB in Carry Flag
131 | //`800F 30 01 JR NC, NOADD
132 | //`8011 19 ADD HL, DE
133 | //`8012 CB 23 NOADD: SLA E
134 | //`8014 CB 12 RL D
135 | //`8016 10 F5 DJNZ MULTI
136 |
137 | var ram = HexFileReader.Read("../../../HexFiles/Multiplication2.hex");
138 |
139 | A.CallTo(() => fakeBus.Read(A._, A._))
140 | .ReturnsLazily((ushort addr, bool ro) => ram[addr]);
141 |
142 | var cpu = new Z80() { A = 0x00, B = 0x00, C = 0x00, H = 0x00, L = 0x00, PC = 0x8000 };
143 | cpu.ConnectToBus(fakeBus);
144 |
145 | while (cpu.PC < 0x8018)
146 | {
147 | cpu.Step();
148 | }
149 |
150 | Assert.Equal(0x0372, cpu.HL);
151 | }
152 |
153 | [Fact]
154 | private void HandleStrayOpCodes2()
155 | {
156 | var fakeBus = A.Fake();
157 |
158 | var program = new Dictionary
159 | {
160 | // Program Code
161 | { 0x0080, 0xDD },
162 | { 0x0081, 0xDD },
163 | { 0x0082, 0xDD },
164 | { 0x0083, 0xFD },
165 | { 0x0084, 0xDD }, // LD IX &1000
166 | { 0x0085, 0x21 },
167 | { 0x0086, 0x00 },
168 | { 0x0087, 0x10 },
169 | { 0x0088, 0x00 },
170 | };
171 |
172 | A.CallTo(() => fakeBus.Read(A._, A._))
173 | .ReturnsLazily((ushort addr, bool ro) => program[addr]);
174 |
175 | var cpu = new Z80() { A = 0x00, IX = 0x00, PC = 0x0080 };
176 | cpu.ConnectToBus(fakeBus);
177 |
178 | while (cpu.PC < 0x0088)
179 | {
180 | cpu.Step();
181 | }
182 |
183 | Assert.Equal(0x1000, cpu.IX);
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/Essenbee.Z80/.cr/images/CF3C03E9AEA956395291BE98C580C27F.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/essenbee/z80emu/8085dc449deb35d8b76ed76e26c4b1347e1286eb/Essenbee.Z80/.cr/images/CF3C03E9AEA956395291BE98C580C27F.png
--------------------------------------------------------------------------------
/Essenbee.Z80/.cr/images/E5D9CCCCCD8B82BB1159F822CFE086F8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/essenbee/z80emu/8085dc449deb35d8b76ed76e26c4b1347e1286eb/Essenbee.Z80/.cr/images/E5D9CCCCCD8B82BB1159F822CFE086F8.png
--------------------------------------------------------------------------------
/Essenbee.Z80/Essenbee.Z80.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | true
6 | Essenbee
7 | Codebase Alpha Live-coding Stream
8 | A Z80 emulator
9 | Git
10 | https://github.com/essenbee/z80emu
11 | https://github.com/essenbee/z80emu
12 | LICENSE
13 | z80
14 | A sample implementation using this code can be found in the repo. It is a ZX Spectrum 48 emulator (without sound).
15 | 1.0.1
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | all
26 | runtime; build; native; contentfiles; analyzers; buildtransitive
27 |
28 |
29 |
30 |
31 |
32 | True
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/Essenbee.Z80/Essenbee.Z80.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29326.143
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Essenbee.Z80", "Essenbee.Z80.csproj", "{7D5005D4-A8F1-4E80-8689-8DDD04264F3A}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Essenbee.Z80.Tests", "..\Essenbee.Z80.Tests\Essenbee.Z80.Tests.csproj", "{A6771D5B-325D-4051-AE66-9C2F1DBE92E8}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Essenbee.Z80.Debugger", "..\Essenbee.Z80.Debugger\Essenbee.Z80.Debugger.csproj", "{2D7F4613-E4A0-4CFB-9560-8171170EC739}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Essenbee.Z80.Sample", "..\Essenbee.Z80.Sample\Essenbee.Z80.Sample.csproj", "{34DB618F-D002-4D32-A15D-59A9539E030C}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essenbee.Spectrum48", "..\Essenbee.Spectrum48\Essenbee.Spectrum48.csproj", "{0B15FF0C-414B-4F4F-81E2-694074257B3B}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {7D5005D4-A8F1-4E80-8689-8DDD04264F3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {7D5005D4-A8F1-4E80-8689-8DDD04264F3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {7D5005D4-A8F1-4E80-8689-8DDD04264F3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {7D5005D4-A8F1-4E80-8689-8DDD04264F3A}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {A6771D5B-325D-4051-AE66-9C2F1DBE92E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {A6771D5B-325D-4051-AE66-9C2F1DBE92E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {A6771D5B-325D-4051-AE66-9C2F1DBE92E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {A6771D5B-325D-4051-AE66-9C2F1DBE92E8}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {2D7F4613-E4A0-4CFB-9560-8171170EC739}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {2D7F4613-E4A0-4CFB-9560-8171170EC739}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {2D7F4613-E4A0-4CFB-9560-8171170EC739}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {2D7F4613-E4A0-4CFB-9560-8171170EC739}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {34DB618F-D002-4D32-A15D-59A9539E030C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {34DB618F-D002-4D32-A15D-59A9539E030C}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {34DB618F-D002-4D32-A15D-59A9539E030C}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {34DB618F-D002-4D32-A15D-59A9539E030C}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {0B15FF0C-414B-4F4F-81E2-694074257B3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {0B15FF0C-414B-4F4F-81E2-694074257B3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {0B15FF0C-414B-4F4F-81E2-694074257B3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {0B15FF0C-414B-4F4F-81E2-694074257B3B}.Release|Any CPU.Build.0 = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | GlobalSection(ExtensibilityGlobals) = postSolution
47 | SolutionGuid = {511CBE2B-5AAD-41EE-A7DC-11E042ABABB5}
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/Essenbee.Z80/IBus.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Essenbee.Z80
4 | {
5 | public interface IBus
6 | {
7 | bool Interrupt { get; set; }
8 | bool NonMaskableInterrupt { get; set; }
9 | IList Data { get; set; }
10 | IReadOnlyCollection RAM { get; }
11 | byte Read(ushort addr, bool ro = false);
12 | void Write(ushort addr, byte data);
13 | byte ReadPeripheral(ushort port);
14 | void WritePeripheral(ushort port, byte data);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Essenbee.Z80/Instruction.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Essenbee.Z80
6 | {
7 | public class Instruction
8 | {
9 | public string Mnemonic { get; set; }
10 | public Func AddressingMode1 { get; set; }
11 | public Func AddressingMode2 { get; set; }
12 | public Func Op { get; set; }
13 | public int TStates { get; set; }
14 | public int MCycles { get; set; }
15 | public List Timing { get; }
16 |
17 | public Instruction(string mnemonic, Func addrMode1, Func addrMode2, Func op,
18 | List timing)
19 | {
20 | if (timing is null)
21 | {
22 | throw new ArgumentNullException($"Parameter {nameof(timing)} cannot be null.");
23 | }
24 |
25 | Mnemonic = mnemonic;
26 | AddressingMode1 = addrMode1;
27 | AddressingMode2 = addrMode2;
28 | Op = op;
29 | Timing = timing;
30 | TStates = timing.Sum();
31 | MCycles = timing.Count;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Essenbee.Z80/InterruptMode.cs:
--------------------------------------------------------------------------------
1 | namespace Essenbee.Z80
2 | {
3 | public enum InterruptMode
4 | {
5 | Mode0 = 0,
6 | Mode1 = 1,
7 | Mode2 = 2,
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Essenbee.Z80/Z80.AddressModes.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Essenbee.Z80
6 | {
7 | public partial class Z80
8 | {
9 | // Address Modes
10 | //
11 | // Z80 address modes refer to how the address of the data an instruction operates on,
12 | // is generated in each instruction. many instructions include more than one operand,
13 | // and in these cases, two types of addressing can be employed. For example, LD can use
14 | // Immediate Mode to specify the source data and Indexed Mode to specify the destination.
15 |
16 | // Implied Mode
17 | private byte IMP() => 0;
18 |
19 | // Indexed Mode
20 | private byte IDX() => 0;
21 |
22 | // Immediate Mode
23 | private byte IMM()
24 | {
25 | _absoluteAddress = PC++;
26 | return 0;
27 | }
28 |
29 | // Relative Addressing
30 | private byte REL()
31 | {
32 | _absoluteAddress = PC++;
33 | return 0;
34 | }
35 |
36 | // Relative Special Addressing
37 | private byte RELS()
38 | {
39 | _absoluteAddress = PC++;
40 | return 0;
41 | }
42 |
43 | // Extended Immediate Mode
44 | private byte IMX()
45 | {
46 | _absoluteAddress = PC++;
47 | return 0;
48 | }
49 |
50 | // Register Addressing:
51 | // The opcode contains bits of information that determine the registers involved
52 | private byte REG() => 0;
53 |
54 | // Register Indirect Addressing - (HL)
55 | private byte RGIHL()
56 | {
57 | _absoluteAddress = HL;
58 |
59 | return 0;
60 | }
61 |
62 | // Bit Addressing
63 |
64 | //
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Essenbee.Z80/Z80.BitGroup.cs:
--------------------------------------------------------------------------------
1 | namespace Essenbee.Z80
2 | {
3 | public partial class Z80
4 | {
5 | // ========================================
6 | // Bit Set, Reset and Test Group
7 | // ========================================
8 |
9 | // Instruction : BIT b,r
10 | // Operation : Z <- true if bit is 0
11 | // Flags Affected : Z,H,N
12 |
13 | private byte BITBR(byte opCode)
14 | {
15 | var src = opCode & 0b00000111;
16 | byte n = ReadFromRegister(src);
17 |
18 | var bit = (opCode & 0b00111000) >> 3;
19 | var result = n & (byte)(1 << bit);
20 |
21 | SetFlag(Flags.Z, result == 0);
22 | SetFlag(Flags.H, true);
23 | SetFlag(Flags.N, false);
24 |
25 | // Undocumented flags
26 | SetFlag(Flags.X, ((n & 0x0008) > 0) ? true : false); //Copy of bit 3
27 | SetFlag(Flags.U, ((n & 0x0020) > 0) ? true : false); //Copy of bit 5
28 | SetFlag(Flags.S, bit == 7 && result != 0);
29 | SetFlag(Flags.P, result == 0);
30 |
31 | SetQ();
32 |
33 | return 0;
34 | }
35 |
36 | // Instruction : BIT b,(HL)
37 | // Operation : Z <- true if bit is 0
38 | // Flags Affected : Z,H,N
39 |
40 | private byte BITHL(byte opCode)
41 | {
42 | byte n = Fetch1(CBInstructions);
43 |
44 | var bit = (opCode & 0b00111000) >> 3;
45 | var result = n & (byte)(1 << bit);
46 |
47 | SetFlag(Flags.Z, result == 0);
48 | SetFlag(Flags.H, true);
49 | SetFlag(Flags.N, false);
50 |
51 | // Undocumented flags
52 | SetFlag(Flags.X, ((MEMPTR & 0x0800) > 0) ? true : false); //Copy of bit 11
53 | SetFlag(Flags.U, ((MEMPTR & 0x2000) > 0) ? true : false); //Copy of bit 13
54 | SetFlag(Flags.S, bit == 7 && result != 0);
55 | SetFlag(Flags.P, result == 0);
56 |
57 | SetQ();
58 |
59 | return 0;
60 | }
61 |
62 | // Instruction : BIT b,(IX+d)
63 | // Operation : Z <- true if bit is 0
64 | // Flags Affected : Z,H,N
65 |
66 | private byte BITIXD(byte opCode)
67 | {
68 | var d = (sbyte)ReadFromBus((ushort)(PC - 2)); // displacement -128 to +127
69 | _absoluteAddress = (ushort)(IX + d);
70 | MEMPTR = (ushort)(IX + d);
71 | var n = Fetch2(DDCBInstructions);
72 |
73 | var bit = (opCode & 0b00111000) >> 3;
74 | var result = n & (byte)(1 << bit);
75 |
76 | SetFlag(Flags.Z, result == 0);
77 | SetFlag(Flags.H, true);
78 | SetFlag(Flags.N, false);
79 |
80 | // Undocumented flags
81 | SetFlag(Flags.X, ((MEMPTR & 0x0800) > 0) ? true : false); //Copy of bit 11
82 | SetFlag(Flags.U, ((MEMPTR & 0x2000) > 0) ? true : false); //Copy of bit 13
83 | SetFlag(Flags.S, bit == 7 && result != 0);
84 | SetFlag(Flags.P, result == 0);
85 |
86 | SetQ();
87 |
88 | return 0;
89 | }
90 |
91 | // Instruction : BIT b,(IY+d)
92 | // Operation : Z <- true if bit is 0
93 | // Flags Affected : Z,H,N
94 |
95 | private byte BITIYD(byte opCode)
96 | {
97 | var d = (sbyte)ReadFromBus((ushort)(PC - 2)); // displacement -128 to +127
98 | _absoluteAddress = (ushort)(IY + d);
99 | MEMPTR = (ushort)(IY + d);
100 | var n = Fetch2(FDCBInstructions);
101 |
102 | var bit = (opCode & 0b00111000) >> 3;
103 | var result = n & (byte)(1 << bit);
104 |
105 | SetFlag(Flags.Z, result == 0);
106 | SetFlag(Flags.H, true);
107 | SetFlag(Flags.N, false);
108 |
109 | // Undocumented flags
110 | SetFlag(Flags.X, ((MEMPTR & 0x0800) > 0) ? true : false); //Copy of bit 11
111 | SetFlag(Flags.U, ((MEMPTR & 0x2000) > 0) ? true : false); //Copy of bit 13
112 | SetFlag(Flags.S, bit == 7 && result != 0);
113 | SetFlag(Flags.P, result == 0);
114 |
115 | SetQ();
116 |
117 | return 0;
118 | }
119 |
120 | // Instruction : RES b,r
121 | // Operation :
122 | // Flags Affected : None
123 |
124 | private byte RESBR(byte opCode)
125 | {
126 | var src = opCode & 0b00000111;
127 | byte n = ReadFromRegister(src);
128 |
129 | var bit = (opCode & 0b00111000) >> 3;
130 | n &= (byte)~(byte)(1 << bit);
131 | AssignToRegister(src, n);
132 |
133 | ResetQ();
134 |
135 | return 0;
136 | }
137 |
138 | // Instruction : RES b,(HL)
139 | // Operation :
140 | // Flags Affected : None
141 |
142 | private byte RESHL(byte opCode)
143 | {
144 | byte n = Fetch1(CBInstructions);
145 |
146 | var bit = (opCode & 0b00111000) >> 3;
147 | n &= (byte)~(byte)(1 << bit);
148 | WriteToBus(HL, n);
149 |
150 | ResetQ();
151 |
152 | return 0;
153 | }
154 |
155 | // Instruction : RES b,(IX+d)
156 | // Operation :
157 | // Flags Affected : None
158 |
159 | private byte RESIXD(byte opCode)
160 | {
161 | var src = opCode & 0b00000111;
162 | var d = (sbyte)ReadFromBus((ushort)(PC - 2)); // displacement -128 to +127
163 | _absoluteAddress = (ushort)(IX + d);
164 | MEMPTR = (ushort)(IX + d);
165 | var n = Fetch2(DDCBInstructions);
166 |
167 | var bit = (opCode & 0b00111000) >> 3;
168 | n &= (byte)~(byte)(1 << bit);
169 | WriteToBus((ushort)(IX + d), n);
170 |
171 | if (src != 6)
172 | {
173 | AssignToRegister(src, n);
174 | }
175 |
176 | ResetQ();
177 |
178 | return 0;
179 | }
180 |
181 | // Instruction : RES b,(IY+d)
182 | // Operation :
183 | // Flags Affected : None
184 |
185 | private byte RESIYD(byte opCode)
186 | {
187 | var src = opCode & 0b00000111;
188 | var d = (sbyte)ReadFromBus((ushort)(PC - 2)); // displacement -128 to +127
189 | _absoluteAddress = (ushort)(IY + d);
190 | MEMPTR = (ushort)(IY + d);
191 | var n = Fetch2(FDCBInstructions);
192 |
193 | var bit = (opCode & 0b00111000) >> 3;
194 | n &= (byte)~(byte)(1 << bit);
195 | WriteToBus((ushort)(IY + d), n);
196 |
197 | if (src != 6)
198 | {
199 | AssignToRegister(src, n);
200 | }
201 |
202 | ResetQ();
203 |
204 | return 0;
205 | }
206 |
207 | // Instruction : SET b,r
208 | // Operation :
209 | // Flags Affected : None
210 |
211 | private byte SETBR(byte opCode)
212 | {
213 | var src = opCode & 0b00000111;
214 | byte n = ReadFromRegister(src);
215 |
216 | var bit = (opCode & 0b00111000) >> 3;
217 | n |= (byte)(1 << bit);
218 | AssignToRegister(src, n);
219 |
220 | ResetQ();
221 |
222 | return 0;
223 | }
224 |
225 | // Instruction : SET b,(HL)
226 | // Operation :
227 | // Flags Affected : None
228 |
229 | private byte SETHL(byte opCode)
230 | {
231 | byte n = Fetch1(CBInstructions);
232 |
233 | var bit = (opCode & 0b00111000) >> 3;
234 | n |= (byte)(1 << bit);
235 | WriteToBus(HL, n);
236 |
237 | ResetQ();
238 |
239 | return 0;
240 | }
241 |
242 | // Instruction : SET b,(IX+d)
243 | // Operation :
244 | // Flags Affected : None
245 |
246 | private byte SETIXD(byte opCode)
247 | {
248 | var src = opCode & 0b00000111;
249 | var d = (sbyte)ReadFromBus((ushort)(PC - 2)); // displacement -128 to +127
250 | _absoluteAddress = (ushort)(IX + d);
251 | MEMPTR = (ushort)(IX + d);
252 | var n = Fetch2(DDCBInstructions);
253 |
254 | var bit = (opCode & 0b00111000) >> 3;
255 | n |= (byte)(1 << bit);
256 | WriteToBus((ushort)(IX + d), n);
257 |
258 | if (src != 6)
259 | {
260 | AssignToRegister(src, n);
261 | }
262 |
263 | ResetQ();
264 |
265 | return 0;
266 | }
267 |
268 | // Instruction : SET b,(IY+d)
269 | // Operation :
270 | // Flags Affected : None
271 |
272 | private byte SETIYD(byte opCode)
273 | {
274 | var src = opCode & 0b00000111;
275 | var d = (sbyte)ReadFromBus((ushort)(PC - 2)); // displacement -128 to +127
276 | _absoluteAddress = (ushort)(IY + d);
277 | MEMPTR = (ushort)(IY + d);
278 | var n = Fetch2(FDCBInstructions);
279 |
280 | var bit = (opCode & 0b00111000) >> 3;
281 | n |= (byte)(1 << bit);
282 | WriteToBus((ushort)(IY + d), n);
283 |
284 | if (src != 6)
285 | {
286 | AssignToRegister(src, n);
287 | }
288 |
289 | ResetQ();
290 |
291 | return 0;
292 | }
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/Essenbee.Z80/Z80.IO.cs:
--------------------------------------------------------------------------------
1 | namespace Essenbee.Z80
2 | {
3 | public partial class Z80
4 | {
5 | // ========================================
6 | // Input/Output Group
7 | // ========================================
8 |
9 | // Instruction : IN A,(n)
10 | // Operation : A <- (n)
11 | // Flags Affected : None
12 | private byte INA(byte opCode)
13 | {
14 | byte port = Fetch1(RootInstructions);
15 |
16 | var addr = (ushort)((A << 8) + port);
17 | var n = ReadFromBusPort(addr);
18 |
19 | MEMPTR = (ushort)(addr + 1);
20 | ResetQ();
21 | A = n;
22 |
23 | return 0;
24 | }
25 |
26 | // Instruction : OUT (n),A
27 | // Operation : (n) <- A
28 | // Flags Affected : None
29 | private byte OUTA(byte opCode)
30 | {
31 | byte port = Fetch1(RootInstructions);
32 |
33 | var addr = (ushort)((A << 8) + port);
34 | WriteToBusPort(addr, A);
35 |
36 | // MEMPTR_low = (port + 1) & #FF, MEMPTR_hi = A
37 | MEMPTR = (ushort)((A << 8) + ((port + 1) & 0xFF));
38 | ResetQ();
39 |
40 | return 0;
41 | }
42 |
43 | // Instruction : IN r,(C)
44 | // Operation : r <- (C)
45 | // Flags Affected : None
46 | private byte INR(byte opCode)
47 | {
48 | var n = ReadFromBusPort(BC);
49 | MEMPTR = (ushort)(BC + 1);
50 | var dest = (opCode & 0b00111000) >> 3;
51 | AssignToRegister(dest, n);
52 |
53 | SetFlag(Flags.N, false);
54 | SetFlag(Flags.H, false);
55 | SetFlag(Flags.P, Parity(n));
56 | SetFlag(Flags.Z, n == 0);
57 | SetFlag(Flags.S, n > 0x7F);
58 |
59 | // Undocumented Flags
60 | SetFlag(Flags.X, ((n & 0x08) > 0) ? true : false); //Copy of bit 3
61 | SetFlag(Flags.U, ((n & 0x20) > 0) ? true : false); //Copy of bit 5
62 |
63 | SetQ();
64 |
65 | return 0;
66 | }
67 |
68 | // Instruction : OUT (C),r
69 | // Operation : (C) <- r
70 | // Flags Affected : None
71 | private byte OUTR(byte opCode)
72 | {
73 | MEMPTR = (ushort)(BC + 1);
74 | var src = (opCode & 0b00111000) >> 3;
75 | var n = ReadFromRegister(src);
76 |
77 | WriteToBusPort(BC, n);
78 |
79 | ResetQ();
80 |
81 | return 0;
82 | }
83 |
84 | // Instruction : INI
85 | // Operation : (HL) <- (C), B <- B - 1, HL <- HL + 1
86 | // Flags Affected : All
87 | private byte INI(byte opCode)
88 | {
89 | var n = ReadFromBusPort(BC);
90 | MEMPTR = (ushort)(BC + 1);
91 | WriteToBus(HL, n);
92 |
93 | IncRegisterPair(HL, 2);
94 | var val = B;
95 | B = (byte)(B - 1);
96 | var decVal = B;
97 |
98 | SetDecFlags(val, decVal);
99 |
100 | var k = n + ((C + 1) & 255);
101 | SetFlag(Flags.H, k > 0xFF);
102 | SetFlag(Flags.C, k > 0xFF);
103 |
104 | var x =(ushort)((k & 7) ^ decVal);
105 | SetFlag(Flags.P, Parity(x));
106 | SetFlag(Flags.N, ((n & 0x80) > 0) ? true : false); //Copy of bit 7
107 |
108 | SetQ();
109 |
110 | return 0;
111 | }
112 |
113 | // Instruction : INIR
114 | // Operation : (HL) <- (C), B <- B - 1, HL <- HL + 1
115 | // Flags Affected : All
116 | private byte INIR(byte opCode)
117 | {
118 | INI(opCode);
119 | byte additionalTStates = 0;
120 |
121 | if (B != 0)
122 | {
123 | PC -= 2;
124 | additionalTStates = 5;
125 | }
126 |
127 | SetQ();
128 |
129 | return additionalTStates;
130 | }
131 |
132 | // Instruction : IND
133 | // Operation : (HL) <- (C), B <- B - 1, HL <- HL - 1
134 | // Flags Affected : All
135 | private byte IND(byte opCode)
136 | {
137 | var n = ReadFromBusPort(BC);
138 | MEMPTR = (ushort)(BC - 1);
139 | WriteToBus(HL, n);
140 |
141 | IncRegisterPair(HL, 2, -1);
142 | var val = B;
143 | B = (byte)(B - 1);
144 | var decVal = B;
145 |
146 | SetDecFlags(val, decVal);
147 |
148 | var k = n + ((C - 1) & 255);
149 | SetFlag(Flags.H, k > 0xFF);
150 | SetFlag(Flags.C, k > 0xFF);
151 |
152 | var x = (ushort)((k & 7) ^ decVal);
153 | SetFlag(Flags.P, Parity(x));
154 | SetFlag(Flags.N, ((n & 0x80) > 0) ? true : false); //Copy of bit 7
155 |
156 | SetQ();
157 |
158 | return 0;
159 | }
160 |
161 | // Instruction : INDR
162 | // Operation : (HL) <- (C), B <- B - 1, HL <- HL - 1
163 | // Flags Affected : All
164 | private byte INDR(byte opCode)
165 | {
166 | IND(opCode);
167 | byte additionalTStates = 0;
168 |
169 | if (B != 0)
170 | {
171 | PC -= 2;
172 | additionalTStates = 5;
173 | }
174 |
175 | SetQ();
176 |
177 | return additionalTStates;
178 | }
179 |
180 | // Instruction : OUTI
181 | // Operation : (C) <- (HL), B <- B - 1, HL <- HL + 1
182 | // Flags Affected : All
183 | private byte OUTI(byte opCode)
184 | {
185 | var n = ReadFromBus(HL);
186 | WriteToBusPort(BC, n);
187 |
188 | IncRegisterPair(HL, 2);
189 | var val = B;
190 | B = (byte)(B - 1);
191 | var decVal = B;
192 |
193 | MEMPTR = (ushort)(BC + 1);
194 | SetDecFlags(val, decVal);
195 |
196 | var k = n + L;
197 | SetFlag(Flags.H, k > 0xFF);
198 | SetFlag(Flags.C, k > 0xFF);
199 |
200 | var x = (ushort)((k & 7) ^ decVal);
201 | SetFlag(Flags.P, Parity(x));
202 | SetFlag(Flags.N, ((n & 0x80) > 0) ? true : false); //Copy of bit 7
203 |
204 | SetQ();
205 |
206 | return 0;
207 | }
208 |
209 | // Instruction : OTIR
210 | // Operation : (C) <- (HL), B <- B - 1, HL <- HL + 1
211 | // Flags Affected : All
212 | private byte OTIR(byte opCode)
213 | {
214 | OUTI(opCode);
215 | byte additionalTStates = 0;
216 |
217 | if (B != 0)
218 | {
219 | PC -= 2;
220 | additionalTStates = 5;
221 | }
222 |
223 | SetQ();
224 |
225 | return additionalTStates;
226 | }
227 |
228 | // Instruction : OUTD
229 | // Operation : (C) <- (HL), B <- B - 1, HL <- HL - 1
230 | // Flags Affected : All
231 | private byte OUTD(byte opCode)
232 | {
233 | var n = ReadFromBus(HL);
234 | WriteToBusPort(BC, n);
235 |
236 | IncRegisterPair(HL, 2, -1);
237 | var val = B;
238 | B = (byte)(B - 1);
239 | var decVal = B;
240 |
241 | MEMPTR = (ushort)(BC - 1);
242 | SetDecFlags(val, decVal);
243 |
244 | var k = n + L;
245 | SetFlag(Flags.H, k > 0xFF);
246 | SetFlag(Flags.C, k > 0xFF);
247 |
248 | var x = (ushort)((k & 7) ^ decVal);
249 | SetFlag(Flags.P, Parity(x));
250 | SetFlag(Flags.N, ((n & 0x80) > 0) ? true : false); //Copy of bit 7
251 |
252 | SetQ();
253 |
254 | return 0;
255 | }
256 |
257 | // Instruction : OTDR
258 | // Operation : (C) <- (HL), B <- B - 1, HL <- HL - 1
259 | // Flags Affected : All
260 | private byte OTDR(byte opCode)
261 | {
262 | OUTD(opCode);
263 | byte additionalTStates = 0;
264 |
265 | if (B != 0)
266 | {
267 | PC -= 2;
268 | additionalTStates = 5;
269 | }
270 |
271 | SetQ();
272 |
273 | return additionalTStates;
274 | }
275 | }
276 | }
277 |
--------------------------------------------------------------------------------
/Essenbee.Z80/Z80.Jump.cs:
--------------------------------------------------------------------------------
1 | namespace Essenbee.Z80
2 | {
3 | public partial class Z80
4 | {
5 | // ========================================
6 | // Jump Group
7 | // ========================================
8 |
9 | // Instruction : JP nn
10 | // Operation : PC <- nn
11 | // Flags Affected : None
12 | private byte JPNN(byte opCode)
13 | {
14 | var loByte = Fetch1(RootInstructions);
15 | var hiByte = Fetch1(RootInstructions);
16 | var addr = (ushort)((hiByte << 8) + loByte);
17 |
18 | PC = addr;
19 |
20 | ResetQ();
21 | MEMPTR = addr;
22 |
23 | return 0;
24 | }
25 |
26 | // Instruction : JP cc, nn
27 | // Operation : PC <- nn if cc is true
28 | // Flags Affected : None
29 | private byte JPCCNN(byte opCode)
30 | {
31 | var loByte = Fetch1(RootInstructions);
32 | var hiByte = Fetch1(RootInstructions);
33 | var addr = (ushort)((hiByte << 8) + loByte);
34 |
35 | var cc = (opCode & 0b00111000) >> 3;
36 |
37 | if (EvaluateCC(cc))
38 | {
39 | PC = addr;
40 | }
41 |
42 | MEMPTR = addr;
43 |
44 | ResetQ();
45 | return 0;
46 | }
47 |
48 | // Instruction : JR e
49 | // Operation : PC <- PC + e
50 | // Flags Affected : None
51 | // Notes : Assembler with compensate automatically for the twice-incremented PC
52 | private byte JR(byte opCode)
53 | {
54 | var e = (sbyte)Fetch1(RootInstructions);
55 |
56 | PC = (ushort)(PC + e);
57 |
58 | ResetQ();
59 | MEMPTR = PC;
60 |
61 | return 0;
62 | }
63 |
64 | // Instruction : JR C, e
65 | // Operation : PC <- PC + e if Carry set
66 | // Flags Affected : None
67 | // Notes : Assembler with compensate automatically for the twice-incremented PC
68 | private byte JRC(byte opCode)
69 | {
70 | byte additionalTStates = 0;
71 | var e = (sbyte)Fetch1(RootInstructions);
72 |
73 | if (CheckFlag(Flags.C))
74 | {
75 | additionalTStates = 5;
76 | PC = (ushort)(PC + e);
77 | MEMPTR = PC;
78 | }
79 |
80 | ResetQ();
81 | return additionalTStates;
82 | }
83 |
84 | // Instruction : JR NC, e
85 | // Operation : PC <- PC + e if Carry not set
86 | // Flags Affected : None
87 | // Notes : Assembler with compensate automatically for the twice-incremented PC
88 | private byte JRNC(byte opCode)
89 | {
90 | byte additionalTStates = 0;
91 | var e = (sbyte)Fetch1(RootInstructions);
92 |
93 | if (!CheckFlag(Flags.C))
94 | {
95 | additionalTStates = 5;
96 | PC = (ushort)(PC + e);
97 | MEMPTR = PC;
98 | }
99 |
100 | ResetQ();
101 | return additionalTStates;
102 | }
103 |
104 | // Instruction : JR Z, e
105 | // Operation : PC <- PC + e if Zero flag set
106 | // Flags Affected : None
107 | // Notes : Assembler with compensate automatically for the twice-incremented PC
108 | private byte JRZ(byte opCode)
109 | {
110 | byte additionalTStates = 0;
111 | var e = (sbyte)Fetch1(RootInstructions);
112 |
113 | if (CheckFlag(Flags.Z))
114 | {
115 | additionalTStates = 5;
116 | PC = (ushort)(PC + e);
117 | MEMPTR = PC;
118 | }
119 |
120 | ResetQ();
121 | return additionalTStates;
122 | }
123 |
124 | // Instruction : JR NZ, e
125 | // Operation : PC <- PC + e if Zero flag not set
126 | // Flags Affected : None
127 | // Notes : Assembler with compensate automatically for the twice-incremented PC
128 | private byte JRNZ(byte opCode)
129 | {
130 | byte additionalTStates = 0;
131 | var e = (sbyte)Fetch1(RootInstructions);
132 |
133 | if (!CheckFlag(Flags.Z))
134 | {
135 | additionalTStates = 5;
136 | PC = (ushort)(PC + e);
137 | MEMPTR = PC;
138 | }
139 |
140 | ResetQ();
141 | return additionalTStates;
142 | }
143 |
144 | // Instruction : JP HL
145 | // Operation : PC <- HL
146 | // Flags Affected : None
147 | // Notes : -
148 | private byte JPHL(byte opCode)
149 | {
150 | PC = HL;
151 |
152 | ResetQ();
153 | return 0;
154 | }
155 |
156 | // Instruction : JP IX
157 | // Operation : PC <- IX
158 | // Flags Affected : None
159 | // Notes : -
160 | private byte JPIX(byte opCode)
161 | {
162 | PC = IX;
163 |
164 | ResetQ();
165 | return 0;
166 | }
167 |
168 | // Instruction : JP IY
169 | // Operation : PC <- IY
170 | // Flags Affected : None
171 | // Notes : -
172 | private byte JPIY(byte opCode)
173 | {
174 | PC = IY;
175 |
176 | ResetQ();
177 | return 0;
178 | }
179 |
180 | // Instruction : DJNZ e
181 | // Operation : Decrement B and jump if NZ (PC <- PC + e)
182 | // Flags Affected : None
183 | // Notes : Assembler with compensate automatically for the twice-incremented PC
184 | private byte DJNZ(byte opCode)
185 | {
186 | byte additionalTStates = 0;
187 | var e = (sbyte)Fetch1(RootInstructions);
188 |
189 | var initialVal = B;
190 | B--;
191 | SetDecFlags(initialVal, B);
192 |
193 | if (!CheckFlag(Flags.Z))
194 | {
195 | additionalTStates = 5;
196 | PC = (ushort)(PC + e);
197 | MEMPTR = PC;
198 | }
199 |
200 | ResetQ();
201 |
202 | return additionalTStates;
203 | }
204 |
205 | // ========================================
206 | // Call and Return Group
207 | // ========================================
208 |
209 | // Instruction : CALL nn
210 | // Operation : Push PC onto Stack, then PC <- nn
211 | // Flags Affected : None
212 |
213 | private byte CALL(byte opCode)
214 | {
215 | var loByte = Fetch1(RootInstructions);
216 | var hiByte = Fetch1(RootInstructions);
217 |
218 | PushProgramCounter();
219 | var addr = (ushort)((hiByte << 8) + loByte);
220 | PC = addr;
221 | MEMPTR = addr;
222 | ResetQ();
223 |
224 | return 0;
225 | }
226 |
227 | // Instruction : CALL cc,nn
228 | // Operation : Conditionally push PC onto Stack, then PC <- nn
229 | // Flags Affected : None
230 |
231 | private byte CALLCC(byte opCode)
232 | {
233 | byte additionalTStates = 0;
234 | var loByte = Fetch1(RootInstructions);
235 | var hiByte = Fetch1(RootInstructions);
236 |
237 | var cc = (opCode & 0b00111000) >> 3;
238 | var addr = (ushort)((hiByte << 8) + loByte);
239 |
240 | if (EvaluateCC(cc))
241 | {
242 | PushProgramCounter();
243 | PC = addr;
244 | additionalTStates = 7;
245 | }
246 |
247 | MEMPTR = addr;
248 | ResetQ();
249 |
250 | return additionalTStates;
251 | }
252 |
253 | // Instruction : RET
254 | // Operation : Pop PC
255 | // Flags Affected : None
256 |
257 | private byte RET(byte opCode)
258 | {
259 | PopProgramCounter();
260 | MEMPTR = PC;
261 | ResetQ();
262 |
263 | return 0;
264 | }
265 |
266 | // Instruction : RET cc
267 | // Operation : Conditionally POP PCn
268 | // Flags Affected : None
269 |
270 | private byte RETCC(byte opCode)
271 | {
272 | byte additionalTStates = 0;
273 | var cc = (opCode & 0b00111000) >> 3;
274 |
275 | if (EvaluateCC(cc))
276 | {
277 | PopProgramCounter();
278 | MEMPTR = PC;
279 | additionalTStates = 6;
280 | }
281 |
282 | ResetQ();
283 |
284 | return additionalTStates;
285 | }
286 |
287 | // Instruction : RETI
288 | // Operation : Return from Interrupt
289 | // Flags Affected : None
290 |
291 | private byte RETI(byte opCode)
292 | {
293 | PopProgramCounter();
294 | MEMPTR = PC;
295 | ResetQ();
296 |
297 | // ToDo: signal interrupting device that interrupt is complete
298 |
299 | return 0;
300 | }
301 |
302 | // Instruction : RETN
303 | // Operation : Return from NMI
304 | // Flags Affected : None
305 |
306 | private byte RETN(byte opCode)
307 | {
308 | PopProgramCounter();
309 | IFF1 = IFF2;
310 | MEMPTR = PC;
311 | ResetQ();
312 |
313 | return 0;
314 | }
315 |
316 | // Instruction : RST p
317 | // Operation : Restart at page zero address p
318 | // Flags Affected : None
319 |
320 | private byte RST(byte opCode)
321 | {
322 | var val = (byte)((opCode & 0b00111000) >> 3);
323 | var addr = GetPageZeroAddress(val);
324 |
325 | PushProgramCounter();
326 | PC = addr;
327 | MEMPTR = addr;
328 | ResetQ();
329 |
330 | return 0;
331 | }
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Stu Bonham
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # z80emu
2 |
3 | 
4 | 
5 | 
6 | 
7 | 
8 |
9 | Building a Z80 emulator in C# 7.3 as a .NET Standard 2.0 Class Library using the Test Driven Development (TDD) mindset. The aim is to create an accurate emulation of the Z80 CPU as a nuget package for general use. We will then go on to use the emulated CPU in a larger project to emulate a particular machine, a ZX Spectrum probably, since it has a very simple architecture, and the project is not really about creating such an emulator. Development will happen both on and off my Twitch live-coding stream, https://twitch.tv/codebasealpha.
10 |
11 | Known as Project Z, coverage of this project begins from Episode 66. Use the link above to follow me on Twitch and be notifed when I am streaming (normally twice a week on Mondays and Wednesdays, but I sometimes run unscheduled pop-up streams).
12 |
13 | **2020-03-15**: Essenbee.Z80 nuget package released!
14 |
15 | ## Sample Project
16 |
17 | The included project *Essenbee.Spectrum48* is an example implementation using the Z80 emulator. It is a very simple and currently quite limited emulation of a ZX Spectrum 48K. It is possible to program in BASIC using this emulation. At the time of writing, I have successfully run the following games on the emulation (as yet, without sound):
18 |
19 | - [X] King's Ransom (an "illustrated" text adventure)
20 | - [X] Dizzy II - Treasure Island Dizzy
21 | - [X] Galaxians (shoot-em up)
22 | - [X] Pyramid (shoot-em up)
23 | - [X] Lords of Midnight (strategy)
24 |
25 | Who knows how sophisticated (or not) it might become over time? If you would like to contribute code for the sample (or any other project), **why not submit a Pull Request?** Check out the Issues list for things I need help with.
26 |
27 | 
28 |
29 | 
30 |
31 | ### YouTube
32 |
33 | The videos for Project Z episodes are archived on YouTube [here](https://www.youtube.com/channel/UCFFtfkaWjMb9UMDpPVnC1Sg). Please subscribe to my YouTube channel to get notified when new videoes are uploaded. Subscribing is free and it helps me out a lot.
34 |
--------------------------------------------------------------------------------